Background sub-agent completion notifications dropped/misrouted into worktree-isolation agents (2.1.179→2.1.183 regression)

Open 💬 2 comments Opened Jun 20, 2026 by jsiles42claude

Summary

Background sub-agent completion notifications are dropped or misrouted when agents are spawned with isolation: "worktree". A worktree-isolation agent never reaches a terminal state in its transcript, so the harness continues to treat it as a live, resumable agent and uses it as a delivery target. When another background agent completes, its task-notification is dequeued into a still-"live" isolation agent (where it surfaces as a misattributed duplicate notification) and removed from the parent/orchestrator's queue — so the spawning agent never receives the completion. Chains that depend on these notifications silently stall.

This is a regression: the same multi-agent workflow was reliable ~2 weeks prior. The running binary is 2.1.183; transcripts were last logged under 2.1.179, which brackets the regression window.

Environment

  • Claude Code 2.1.183 (CLI), Linux
  • Workflow: a foreground orchestrator spawns multiple run_in_background: true sub-agents. The long-lived ones use isolation: "worktree"; short-lived phase agents do not.

Repro (minimal)

  1. Spawn ≥2 background agents with isolation: "worktree" and leave them at-rest (they finish their turn with end_turn but no terminal result/TaskStop record).
  2. Spawn a separate non-isolation background agent and let it complete.
  3. Observe the parent's incoming notifications.

Expected: the non-isolation agent's task-notification (status=completed) is delivered to the parent, on that agent's own task-id.

Actual: the notification is removed from the parent's queue and injected into an at-rest isolation agent's transcript; that isolation agent then "comes to rest" and emits a duplicate notification under its id, describing the other agent's work. The parent either never sees the completion (chain stalls) or sees it misattributed to the wrong agent.

Evidence (from session transcripts)

Notification delivery is an internal queue in the main-session transcript. Census over one run:

  • 43 enqueue, 16 dequeue (delivered), 27 remove (silently discarded).
  • Every remove is timestamp-paired to an inject into an isolation agent. Examples (same notification, ~10–30ms apart): remove(main) of phase task → inject into isolation agent ac62b189 at 13:28:06.804.818; same pattern for four more siblings folding into ac62b189 (which absorbed 5 foreign notifications) and 4 into another isolation agent.
  • A phase agent that finished cleanly (end_turn + full result) had its notification enqueued in main at 13:15:38.132 and removed 27ms later at 13:15:38.159, delivered to nobody — the chain stalled until an out-of-band poll caught it. This is a delivery failure, not a generation failure.
  • The discriminator is worktreePath in the agent's meta.json: only isolation agents act as sinks. dequeues target only isolation-agent ids; removes hit the non-isolation phase agents.

The harness's own note text on these notifications — "fires each time this agent comes to rest with no live background children of its own" — is the routing rule misfiring: the isolation agent's at-rest notification re-emits every time a sibling settles, because the queue is using it as the delivery target.

Ruled out as causes: no Stop / SubagentStop / Notification hooks configured (only an unrelated PostToolUse lint hook).

Suspected cause

A change in 2.1.18x background-task notification routing: an agent in the worktree-isolation "live but at-rest, no terminal record" state is treated as a valid delivery target for other agents' completion notifications. The fix is presumably to (a) give isolation agents a definitive terminal state when their turn ends with no pending children, and/or (b) never route a completion to an agent other than the one that spawned the completing task.

Impact

Any orchestration that fans out background agents and chains on their completions becomes unreliable: steps stall, and operators see duplicate/misattributed "done" messages. The only workaround is an external polling loop reconciling against ground truth, or avoiding isolation: "worktree" entirely.

What would confirm the trigger

Run the minimal repro on 2.1.179 vs 2.1.183 and diff the queue-op sequence. If 2.1.179 shows dequeue-to-parent where 2.1.183 shows remove + inject-to-isolation, that pins the regression to the routing change and confirms isolation-agent liveness as the trigger.

Secondary (separate, lower priority)

/clear mid-session leaves the tmp task directory keyed to the pre-clear session id while new agent transcripts write under the post-clear session id (e.g. task output_file symlinks under …/<old-session>/tasks/ pointing into …/<new-session>/subagents/). Not the cause of the dropped notifications here, but a latent split-brain hazard worth re-keying on /clear.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗