You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Background Task Notifications Not Delivered When Multiple Agents Complete Simultaneously (v2.1.19) #20754
When running multiple background agents (run_in_background: true) in parallel, completion notifications are not consistently delivered to the main session. Only 1 out of 3 agents sent a notification upon completion.
Environment
Claude Code Version: 2.1.19
OS: macOS Sequoia 15.2 (Darwin 25.2.0)
Model: claude-opus-4-5-20251101
Terminal: iTerm2
Steps to Reproduce
Launch 3 background agents simultaneously:
Task({subagent_type: "general-purpose",description: "Task 1",prompt: "...",run_in_background: true})// Repeat for Task 2 and Task 3
While agents are running, send a user message (e.g., "checking status")
Wait for all agents to complete
Expected Behavior
All 3 agents should deliver <task-notification> messages upon completion.
Actual Behavior
Agent 1 (a34bb07): ✅ Notification received
Agent 2 (a0fc025): ❌ No notification (but completed successfully)
Agent 3 (a194492): ❌ No notification (but completed successfully)
All agents completed successfully (verified via tail on output files), but only 1 notification was delivered to the main session.
This notification delivery bug is brutal for multi-agent workflows. If you can't rely on completion notifications, you're back to polling - which defeats the whole point of background agents.
The Root Problem
Claude's internal notification system is:
Best-effort delivery (not guaranteed)
Same-process bound (notifications via terminal output)
No external notification hooks
No mobile notifications
When multiple agents complete simultaneously, the notification queue likely gets overwhelmed or race-conditions occur.
External Notification Layer
We built ForkOff specifically to solve unreliable internal notifications:
How It Works
Instead of relying on Claude's stdout for notifications:
Agent completion → fires webhook to external relay
Relay → guaranteed push notification to mobile
Phone buzzes → "Agent X completed"
Works every time (not best-effort)
Architecture Benefits
Decoupled from Claude:
Notifications don't depend on Claude's internal event system
Works even if Claude crashes/hangs
Can notify multiple devices
Audit trail of all completions
Webhook-based:
# In agent completion script
curl -X POST https://relay.forkoff.app/notify \
-d "agent_id=$AGENT_ID" \
-d "status=complete" \
-d "result=$RESULT"
More evidence.. Same root symptom, confirmed across 2.1.170 through 2.1.178.
What's Wrong?
A completed background/async task intermittently fails to resume the agent waiting on it. Two observed manifestations, same user-visible result:
(a) Misroute: the completion is injected as a turn into a different, already-finished sibling agent instead of the launcher. The launcher never hears about it; the sibling is spuriously revived and emits a mislabeled notification.
(b) No-resume: the completion is delivered into the correct agent's transcript but never triggers the next turn, so the agent stalls on a finished result.
Either way the waiting agent hangs indefinitely until killed or manually nudged. It is load-dependent: the more tasks complete near-simultaneously, the higher the failure rate.
What Should Happen?
Every completed task resumes the agent that launched it, exactly once, in that agent's own context.
Error Logs/Messages
Manifestation (b): minimal repro, two sibling agents on v2.1.170, identical sleep 15 && echo task, diverging only at resume:
healthy agent
stuck agent
Bash issued
16:07:49
16:07:51
TOOL_RESULT delivered
16:08:04 MARKER_1 (is_error:false)
16:08:06 MARKER_4 (is_error:false)
resumed to next turn
16:08:06 "MARKER_1 done" (ok)
never; stalled 6 min until process timeout
The stuck agent's successful tool result is present in its transcript; the loop was simply never re-invoked. No rate-limit/429/quota/error anywhere in its transcript or stderr.
Manifestation (a): main-session queue log (v2.1.177), completion removed without delivery, then surfacing inside a sibling:
enqueue -> MAIN notif{ task=A, tuid=A } # correct ids/content
remove <- MAIN # pulled out, NOT dequeued/delivered
-> injected as a user turn inside already-finished sibling B
enqueue -> MAIN notif{ task=B, "B's name" } # B's identity, A's content (mislabeled)
Steps to Reproduce
From a session, launch 5 or more background agents (run_in_background) at once, each doing a short identical task (e.g. sleep 15 && echo MARKER_<n>), then instruct the launcher to wait for all completions.
Observe that one or more agents finish but never resume / never report back, and the launcher stalls. In headless -p runs, the stalled child is eventually killed by timeout (exit 124).
This is a race with a low per-task rate. A single 5-agent trial frequently does not trigger it (a known-affected version ran clean in one such trial). Reliable detection needs many concurrent tasks and/or repeated trials.
Objective check (do not trust the model's narration): in the session JSONL, grep queue-operation entries with "operation":"remove" on a <task-notification>; and scan subagents/agent-*.jsonl for a <task-notification> whose task-id does not match that file's agent id but is an agent the main session launched. Either is a confirmed instance.
Last Working Version
2.1.152, the most recent version I have observed working with background agents (17 launches across 3 sessions, 0 failures). The bug is present in 2.1.170, 2.1.177, and 2.1.178. I have no background-agent usage between 2.1.153 and 2.1.176, so the exact regression point is unknown.
Additional Information
Not caused by nested subagents (2.1.172). Reproduced cleanly on 2.1.170, which predates that feature, so this should not be treated as a nesting regression.
Scope in real usage: 31 confirmed sibling-misroutes (manifestation a) across heavily-concurrent sessions on 2.1.177; 0 in sessions that ran agents serially. The no-resume variant (b) reproduced live on 2.1.170 and 2.1.178.
Strongly correlated with concurrency: a just-completed sibling still resident when the next task finishes.
Regression data from v2.1.211 (controlled drills, interactive harness, macOS): background-Bash park-wake delivery has degraded from "unreliable" to effectively zero.
Setup: subagents pinned model="sonnet" under an Opus main, each running a short sleep via Bash run_in_background=true and ending their turn parked. Verified from the agent JSONL transcripts (presence/absence of the isMeta background-task wake event), not from agent self-reports.
Simultaneous completions (3 agents, identical sleeps, launched together — the known-worst case): 0/3 woke. All three transcripts end at the parked turn with zero wake events; each sat parked until manually bumped via SendMessage.
Serialized completions (4 agents run strictly one-at-a-time in a quiet session with short sleeps — the favorable case): 0/4 woke. Same signature.
Historical baseline on earlier builds was ~1-in-3 delivered (field measurements, July 2026), so this looks like a further regression, not just the known simultaneity failure mode.
Control: the child-Agent completion channel works — parked agents woken by their own Task/Agent child completing resumed 4/4 in the same drills, including one at nesting depth 2.
Headless claude -p (same version): parked subagents never wake on either channel — neither a background-Bash completion nor a child-agent completion resumes them, whether the headless main is blocked in one long tool call or idling across many short ones. They stay frozen until the process exits.
In every case the completed notification did reach the root session, so the wake-routing (not the completion detection) appears to be where delivery dies. Happy to provide session/agent IDs or run variations if useful.
Adding a reproducible variant: nested background task inside a background subagent never wakes it — result stranded until an external message arrives
We hit what appears to be an aggravated variant of this issue (and of the race described in #39632), in an interactive session on v2.1.191 (macOS).
Setup
Main session spawns a subagent (teammate) via the Agent tool with run_in_background: true, to watch a k8s deployment rollout.
The subagent starts a long-running polling script with Bash run_in_background: true, then ends its turn and goes idle (emits idle_notification).
The script completes normally ~8 minutes later (exit 0, output file fully written to the session tasks dir at 11:25:44).
Observed
The task-completion notification never woke the idle subagent. No result processing, no message back to the main session.
The result sat stranded in the tasks dir for ~21 minutes, until the user noticed the deployment had long finished and the main session manually sent the subagent a message via SendMessage. Only then did it
wake up — at which point TaskStop reported the task no longer existed (already completed and cleaned up).
Control group, same session, same day: two background Bash tasks started directly by the main session (CI build waiters, 10–20 min each) both delivered their completion notifications promptly and reliably. So
notification delivery to the main session works; it's the idle background subagent that never gets woken.
Impact
Any orchestration pattern of the form "background teammate babysits a long-running script and reports back" silently fails: the work completes, but the result never returns. In our run, 3 out of 4 spawned
teammates failed to proactively report results (this one being the worst case). That makes background subagents unreliable as autonomous workers for anything long-running.
Same defect surface as #20754 (background-task completion notification not delivered), but here there is no multi-agent concurrency race — a single background Workflow with two subagents that finished sequentially (~5 min apart), and the main loop still never received a completion notification.
What happened
Both subagents (Architect + Critic) completed normally, each submitting findings via StructuredOutput (~4KB JSON each); transcripts end with "Structured output provided successfully".
The workflow's journal.jsonl has both result events written, but no completed event, and went silent for 14+ minutes after the last write.
The main-thread transcript stopped writing right after the workflow was launched (TaskCreate/Update) and has stayed parked waiting for <task-notification>; the task spinner in the UI accumulates wall-clock indefinitely.
This case is a single Workflow, agents completing at different times, no user message mid-run — yet delivery was still lost. That suggests the failure is not only queue concurrency: the delivery path can silently drop the "done -> notify main loop" step on its own.
A large StructuredOutput result (critic ~4KB JSON in one call) may be an aggravating factor.
Suggested fix
Add a watchdog on the completion-delivery step for background workflows / background tasks: if the computation finishes but the notification is not delivered within a timeout, surface an explicit error and point to journal.jsonl/the transcripts — instead of letting the main-loop spinner spin forever. The journal is effectively the source of truth; when the notification is lost, state should also be derivable from it and the main loop resumed from there.
2.1.243's background-bash wake fix verified on our side — with a same-night A/B against 2.1.241, and two boundaries worth noting.
Setup: unnamed background subagents (Task tool) each fire one run_in_background Bash (sleep 45–390s) and end their turn parked. We then adjudicate each agent's own transcript (subagents/agent-*.jsonl) rather than trusting self-reports: a wake = an isMeta background-task event whose tool_use_id matches the agent's own backgrounded Bash call, followed by a post-wake assistant turn.
2.1.243: 14/14 owner-wakes, across simultaneous completions (two landing 1 s apart), serialized completions, depth-2 (manager under manager), >5-minute parks (390 s at depth 1 AND depth 2), a loaded system (8 concurrent agents), and two overlapping bashes per agent — each completion produced its own wake, so "their last background Bash task" in the changelog is not a limiter.
2.1.241 control, same night, same machine (archived binary from ~/.local/share/claude/versions/ + DISABLE_AUTOUPDATER=1): 0/3 — the parked agents never woke, and the completion notifications reached neither the agents nor the root session (queued, never delivered; a later manual resume flushed them).
Two boundaries observed on 2.1.243:
Named teammates (in_process_teammate) still get no wake — 0/2, still parked 10+ minutes after their bash exited 0. If the fix was meant to cover them, that half isn't working; if "background subagents" is literal, this is just the scope line.
Resumed-child completion routing is unchanged (possibly out of scope for this issue): after a parent SendMessage-resumes a completed child, that child's next completion routed to the ROOT session instead of the resuming parent in 5 of 6 instrumented trials (nondeterministic).
Happy to share transcript excerpts or the drill procedure if useful.
Activity
ForkyTheBot commentedon Feb 20, 2026
This notification delivery bug is brutal for multi-agent workflows. If you can't rely on completion notifications, you're back to polling - which defeats the whole point of background agents.
The Root Problem
Claude's internal notification system is:
When multiple agents complete simultaneously, the notification queue likely gets overwhelmed or race-conditions occur.
External Notification Layer
We built ForkOff specifically to solve unreliable internal notifications:
How It Works
Instead of relying on Claude's stdout for notifications:
Architecture Benefits
Decoupled from Claude:
Webhook-based:
Guaranteed delivery:
Use Case: Your 3-Agent Scenario
With current system:
With external notifications:
Beta Access
Just launched: https://forkoff.app (TestFlight)
Workaround Until Claude Fixes This
Add webhook calls to your agent completion logic as a safety net. Even when Claude's notifications work, you get redundant external notification.
When they fail (like this bug), external system is backup.
Disclosure: I work on ForkOff. Built this after hitting the same "where's my notification?" frustration.
Ipiano commentedon Jun 16, 2026
More evidence.. Same root symptom, confirmed across 2.1.170 through 2.1.178.
What's Wrong?
A completed background/async task intermittently fails to resume the agent waiting on it. Two observed manifestations, same user-visible result:
Either way the waiting agent hangs indefinitely until killed or manually nudged. It is load-dependent: the more tasks complete near-simultaneously, the higher the failure rate.
What Should Happen?
Every completed task resumes the agent that launched it, exactly once, in that agent's own context.
Error Logs/Messages
Manifestation (b): minimal repro, two sibling agents on v2.1.170, identical
sleep 15 && echotask, diverging only at resume:BashissuedMARKER_1(is_error:false)MARKER_4(is_error:false)"MARKER_1 done"(ok)The stuck agent's successful tool result is present in its transcript; the loop was simply never re-invoked. No rate-limit/429/quota/error anywhere in its transcript or stderr.
Manifestation (a): main-session queue log (v2.1.177), completion
removed without delivery, then surfacing inside a sibling:Steps to Reproduce
run_in_background) at once, each doing a short identical task (e.g.sleep 15 && echo MARKER_<n>), then instruct the launcher to wait for all completions.-pruns, the stalled child is eventually killed by timeout (exit 124).Objective check (do not trust the model's narration): in the session JSONL, grep
queue-operationentries with"operation":"remove"on a<task-notification>; and scansubagents/agent-*.jsonlfor a<task-notification>whosetask-iddoes not match that file's agent id but is an agent the main session launched. Either is a confirmed instance.Last Working Version
2.1.152, the most recent version I have observed working with background agents (17 launches across 3 sessions, 0 failures). The bug is present in 2.1.170, 2.1.177, and 2.1.178. I have no background-agent usage between 2.1.153 and 2.1.176, so the exact regression point is unknown.
Additional Information
docs(claude): cite upstream task_notification delivery-bug issues in …
postoso commentedon Jul 16, 2026
Regression data from v2.1.211 (controlled drills, interactive harness, macOS): background-Bash park-wake delivery has degraded from "unreliable" to effectively zero.
Setup: subagents pinned
model="sonnet"under an Opus main, each running a shortsleepviaBash run_in_background=trueand ending their turn parked. Verified from the agent JSONL transcripts (presence/absence of theisMetabackground-task wake event), not from agent self-reports.claude -p(same version): parked subagents never wake on either channel — neither a background-Bash completion nor a child-agent completion resumes them, whether the headless main is blocked in one long tool call or idling across many short ones. They stay frozen until the process exits.In every case the
completednotification did reach the root session, so the wake-routing (not the completion detection) appears to be where delivery dies. Happy to provide session/agent IDs or run variations if useful.zhanghuafei commentedon Jul 17, 2026
Adding a reproducible variant: nested background task inside a background subagent never wakes it — result stranded until an external message arrives
We hit what appears to be an aggravated variant of this issue (and of the race described in #39632), in an interactive session on v2.1.191 (macOS).
Setup
Observed
wake up — at which point TaskStop reported the task no longer existed (already completed and cleaned up).
notification delivery to the main session works; it's the idle background subagent that never gets woken.
Impact
Any orchestration pattern of the form "background teammate babysits a long-running script and reports back" silently fails: the work completes, but the result never returns. In our run, 3 out of 4 spawned
teammates failed to proactively report results (this one being the worst case). That makes background subagents unreliable as autonomous workers for anything long-running.
repair a lost background-shell WAKE too, not just a lost agent report
fix(server): a relayed completion draws its wake divider like a deliv…
Burlesque1 commentedon Aug 21, 2026
Additional evidence (v2.1.233, background Workflow + StructuredOutput schema):
Same defect surface as #20754 (background-task completion notification not delivered), but here there is no multi-agent concurrency race — a single background Workflow with two subagents that finished sequentially (~5 min apart), and the main loop still never received a completion notification.
What happened
journal.jsonlhas bothresultevents written, but nocompletedevent, and went silent for 14+ minutes after the last write.<task-notification>; the task spinner in the UI accumulates wall-clock indefinitely.Evidence
wf_555bb1aa-709(session733ee9da-4fa6-4bd3-9651-9c22bb78e358)subagents/workflows/wf_555bb1aa-709/journal.jsonl: tworesultevents, nocompletedagent-*.jsonltranscripts end with a successful StructuredOutputHow this differs from / adds to #20754
Suggested fix
Add a watchdog on the completion-delivery step for background workflows / background tasks: if the computation finishes but the notification is not delivered within a timeout, surface an explicit error and point to
journal.jsonl/the transcripts — instead of letting the main-loop spinner spin forever. The journal is effectively the source of truth; when the notification is lost, state should also be derivable from it and the main loop resumed from there.postoso commentedon Aug 25, 2026
2.1.243's background-bash wake fix verified on our side — with a same-night A/B against 2.1.241, and two boundaries worth noting.
Setup: unnamed background subagents (Task tool) each fire one
run_in_backgroundBash (sleep 45–390s) and end their turn parked. We then adjudicate each agent's own transcript (subagents/agent-*.jsonl) rather than trusting self-reports: a wake = anisMetabackground-task event whosetool_use_idmatches the agent's own backgrounded Bash call, followed by a post-wake assistant turn.~/.local/share/claude/versions/+DISABLE_AUTOUPDATER=1): 0/3 — the parked agents never woke, and the completion notifications reached neither the agents nor the root session (queued, never delivered; a later manual resume flushed them).Two boundaries observed on 2.1.243:
in_process_teammate) still get no wake — 0/2, still parked 10+ minutes after their bash exited 0. If the fix was meant to cover them, that half isn't working; if "background subagents" is literal, this is just the scope line.SendMessage-resumes a completed child, that child's next completion routed to the ROOT session instead of the resuming parent in 5 of 6 instrumented trials (nondeterministic).Happy to share transcript excerpts or the drill procedure if useful.