Task notifications leak between concurrent sessions in the same cwd (Monitor + background Bash)
Summary
Task notifications (background Bash via run_in_background and Monitor tool streaming events) leak between concurrent Claude Code sessions running in the same cwd on the same macOS user account. The leaked notifications arrive as <task-notification> user messages in the unrelated session's conversation, causing that session's LLM to see events it did not initiate.
This breaks session isolation, pollutes context, and can surface sensitive command output / paths from Session A into Session B.
Environment
- Claude Code: 2.1.108 CLI (reproduced on 2.1.107 in on-disk session logs)
- macOS: 26.4 (build 25E246) on Apple Silicon Mac Mini
- Shell: zsh
- Setup: two VS Code windows open, each with an integrated-terminal tab running
claudein a different (or same) project cwd.
Concrete evidence from my machine
Session A (owner):
- cwd:
/Users/smith/Code/ServiceCentric - sessionId:
227bfe29-8410-4fdd-95ac-9d2633b57ef9
Session B (unrelated, concurrent):
- cwd:
/Users/smith/Code/ServiceCentric(same cwd as A) - sessionId:
10ecd45a-7642-450d-9d3a-b938d1f52a9d
Session A started a background Bash task with id bjs5adjgu (a git ls-remote / "List remote branches" call). Its output file lives at:
/private/tmp/claude-501/-Users-smith-Code-ServiceCentric/227bfe29-8410-4fdd-95ac-9d2633b57ef9/tasks/bjs5adjgu.output
Note the session id 227bfe29-... embedded in the path — this unambiguously identifies A as the owner.
In Session B's conversation log (~/.claude/projects/-Users-smith-Code-ServiceCentric/10ecd45a-...jsonl) the notification appears twice — once as an internal queue enqueue and once as a real user-role message delivered to the LLM:
{"type":"queue-operation","operation":"enqueue","timestamp":"2026-04-14T20:56:35.744Z",
"sessionId":"10ecd45a-7642-450d-9d3a-b938d1f52a9d",
"content":"<task-notification>\n<task-id>bjs5adjgu</task-id>\n<tool-use-id>toolu_01N3V6wkUetpVHqYhWZFcjBC</tool-use-id>\n<output-file>/private/tmp/claude-501/-Users-smith-Code-ServiceCentric/227bfe29-8410-4fdd-95ac-9d2633b57ef9/tasks/bjs5adjgu.output</output-file>\n<status>failed</status>\n<summary>Background command \"List remote branches\" failed with exit code 128</summary>\n</task-notification>"}
{"type":"user","role":"user","origin":{"kind":"task-notification"},
"timestamp":"2026-04-14T20:57:32.435Z",
"sessionId":"10ecd45a-7642-450d-9d3a-b938d1f52a9d",
"message":{"content":"<task-notification>\n<task-id>bjs5adjgu</task-id>\n<tool-use-id>toolu_01N3V6wkUetpVHqYhWZFcjBC</tool-use-id>\n<output-file>/private/tmp/claude-501/-Users-smith-Code-ServiceCentric/227bfe29-.../tasks/bjs5adjgu.output</output-file>\n<status>failed</status>..."}}
Same task-id and tool-use-id, same output-file owned by Session A — but landed in Session B's sessionId, in Session B's project-log directory, surfaced to Session B's LLM as a user message.
Symptoms observed in practice
- While running
/pr-feedbackin Session X, I called the Monitor tool to poll for Copilot re-review activity every 30s. Session X received the heartbeat notifications as expected. But I also observed Session Y (running in another VS Code terminal) receiving the same notifications (<task-notification>entries describing my PR's check events). Session Y had no business seeing them. - Background Bash commands started via
run_in_background: truein Session A produce<task-notification>messages in Session B's chat window.
Expected behavior
A task notification should only deliver to the session that created the task. Task IDs, tool-use IDs, and output-file paths are all scoped per-session, so the routing layer has the information to honor session boundaries.
Actual behavior
Notifications fan out to every Claude Code session whose cwd matches the originating session's cwd (evidence so far is same-cwd). The notifications are enqueued via queue-operation and then replayed as user-role messages with origin.kind: "task-notification", which means the unrelated session's LLM sees and can act on events it did not initiate.
Suspected root cause
The tmp task directory layout is keyed by a cwd slug:
/private/tmp/claude-501/<cwd-slug>/<session-id>/tasks/<task-id>.{output,meta}
But whatever watcher/dispatcher surfaces task events to the foreground conversation appears to enumerate the cwd-slug level and notify all sessions under it, rather than filtering to the <session-id> subdirectory that actually owns the task.
Repro steps
- Open two VS Code windows, each with a terminal in the same project directory (e.g.
cd ~/some-repo). - Run
claudein both terminals to get two concurrent sessions. - In Session A, ask Claude to start any background task, e.g.
run a small gh api call in the background(triggersrun_in_background: true). - Observe Session B's chat: a
<task-notification>describing A's background task appears as if the user sent it. - Same happens with the Monitor tool — Session B receives every Monitor stdout event from A.
The effect is more dramatic with Monitor because it emits continuously: every heartbeat line from Session A's polling loop shows up in Session B.
Impact
- Session isolation broken. The LLM in an unrelated session sees events from another session's work.
- Context pollution. A Monitor that emits every 30s burns through the unrelated session's context with events it cannot act on.
- Confidentiality risk.
<task-notification>includes<summary>and<output-file>paths. The summary often embeds the command description (e.g. "Background command 'List remote branches' failed with exit code 128"), which can leak information about private repos or ongoing work in one session into another session that may belong to a different task/project. - Operator confusion. As the reporting user put it: "I have another Claude Code session running on this machine and it's seeing the monitor output from this session" — which is exactly what's happening, and exactly what broke trust in the Monitor tool for them.
Proposed fix
Route notifications strictly by sessionId. The task-directory structure already records the session id as /private/tmp/claude-501/<cwd-slug>/<session-id>/tasks/... — the dispatcher should read <session-id> and deliver only to that session's queue, rather than broadcasting to every session under <cwd-slug>.
If there's a legitimate reason to share the cwd-level directory (e.g. concurrent monitor access), the fix can stay at the delivery layer: check the task's owning session id before enqueuing into a session's inbox.
Workaround
None that a user can configure. The harness unconditionally delivers task notifications to every same-cwd session. Users can avoid the issue only by running at most one claude instance per cwd.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗