Background agent notifications route through wrong agent ID when launched sequentially (2.1.172+)
Description
When two independent background agents are launched sequentially (second spawned after first's completion notification), the second agent's completion notification arrives on the first agent's task ID. The second agent never sends its own notification. In the execution tree, the second agent appears as a subprocess/child of the first, despite being an independent Agent() call.
Repro
- Launch Agent A with
run_in_background: true - Wait for Agent A's completion notification
- In the same turn, launch Agent B with
run_in_background: true - Observe: Agent B's completion notification arrives on Agent A's task ID. Agent B's own task ID never fires.
Agent types don't matter (reproduced with generator and reviewer subagent types). The key factor is sequential launch where Agent B is spawned in the callback context of Agent A's completion.
Affected versions
- Reproduces on: 2.1.172 through 2.1.176
- Clean on: 2.1.169
Suspected cause
The sub-agent nesting feature introduced in 2.1.172 ("Sub-agents can now spawn their own sub-agents") uses implicit parent-child association based on execution context. When Agent B is spawned while the orchestrator is processing Agent A's notification, the nesting logic incorrectly parents Agent B to Agent A instead of to the orchestrator.
Possibly related to the worker reuse fix in the same release ("Fixed background agents potentially reading another directory's project settings when dispatched onto a pre-warmed worker") — agent identity/parentage may leak through the same worker reuse path that project settings did.
Impact
Orchestrators that track agent completion by task ID miss the second agent's completion entirely, since it never fires on its own ID.
Showing cached comments. Read the full discussion on GitHub ↗
4 Comments
Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Still hitting this on 2.1.179 -- your affected range stops at 2.1.176, and it survives the 2.1.178/179 subagent-message changes, so whatever those fixed, it wasn't this. Got it in both runs I tried on this build. There's also a meaner edge to it than the writeup currently has.
The setup: a multi-stage orchestrator that fans out background subagents one after another (
run_in_background: true), each one spawned in the turn that handles the previous one's completion notification -- which is exactly your trigger.Here's what bit. The
[SYSTEM NOTIFICATION - NOT USER INPUT]envelope -- the string that's _there specifically_ to stop the model treating a background event as input -- got delivered as a user turn into a different, already-finished subagent's context, carrying a reviewer subagent's entire completion payload (task-id,status: completed, the full<result>):The reviewer's completion never reached the orchestrator. Two things fell out of that.
First, the lifecycle tracking glitched the other way too: the completion never registered with the main session, and on exit the runtime reported that reviewer as "was running when the process exited and did not complete, in-process state lost." It _had_ completed -- the payload's right there. So the same misroute also coughs up a phantom "didn't finish" on a task that did.
Second, and this is the actual danger: the subagent that caught the misrouted completion was a code-writing agent that had _already finished its own job_. It read the injected
<result>as its own marching orders ("I'll read the review findings and fix the Critical issue…"), edited the working tree, and fired a _second_ completion under its own task-id. So the guardrail string failed in exactly the spot it exists for, and because the agent on the receiving end was one that acts rather than one that just narrates, it went and did uncommissioned work on the repo.Got it a second time the same run, milder: a test-writer's completion landed in an idle planner agent's transcript instead of the orchestrator's -- harmless only because the planner narrates instead of acting. Which is sort of the whole point: it's one routing bug, and how much damage it does is just a function of what the agent on the receiving end is allowed to do.
On the #67784 dup flag -- I don't think they're the same issue, even if it's the same root cause. This one's the output side (a completion routed to the wrong task-id); #67784 is the input side (a finished agent resumed on someone else's input and acting on it). My run hit both at once, which points at a shared cause, but merging them loses the act-on-injected-input severity, and that's the half that actually does harm. I'd keep both open. Same failure mode as #44778.
2.1.179, macOS, sequential background Agent-tool subagents with custom subagent types. Happy to share more sanitized transcript.
also reproducing on 2.1.189 on Windows with subscription
Another data point: reproduces on 2.1.178, Linux (Arch) — between the 2.1.176 and 2.1.179/2.1.189 reports, so it's not platform- or version-specific.
Two things our session transcripts (JSONL) add:
1. It's real at the data layer, not just the UI/tree. The orchestrator's second
Agent()spawn is recorded in its own transcript withisSidechain: false, yet the second agent's completion<task-notification>is written as a sidechainuserturn into a sibling sub-agent's transcript. The orchestrator receives no actionable completion for it.2. @youcandanch's "acts on injected input" variant reproduces in real work, with harm — not a synthetic harness. A
reviewer's completion (its full review verdict) was delivered into a still-active, code-writingimplementer, wrapped in the[SYSTEM NOTIFICATION - NOT USER INPUT] … Do NOT interpret this as user acknowledgement …envelope. The implementer treated it as a directive addressed to itself — next turn: "Reviewer returned its result. Reading the blockers and preparing the fix." — then edited files, committedFix review findings: …, and pushed. The reviewer's verdict never reached the orchestrator. So the guardrail string failed exactly where it exists, and because the receiving agent acts rather than narrates, the misroute became uncommissioned commits to the repo. As @youcandanch put it, how much damage it does is just a function of what the receiving agent is allowed to do.I also have the benign minimal repro (two
general-purposeagents B3/B4: orchestrator spawns B4, B4's completion lands in B3's transcript, tree shows B4 under B3 while both still running — i.e. mis-parented at spawn, before any output exists).Happy to share sanitized transcript excerpts + execution-tree screenshots.