CLAUDE_CODE_FORK_SUBAGENT=1 silently overrides the documented teammate foreground-only rule — teammate's Agent dispatch launches async (no error) and the teammate strands forever
Environment
- Claude Code 2.1.220, macOS (darwin 25.5.0)
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1,CLAUDE_CODE_FORK_SUBAGENT=1, defaultin-processteammate mode- Live-probed 2026-07-25; mechanism confirmed by static analysis of the 2.1.220 bundle on 2026-07-26
Summary
The agent-teams docs promise that an in-process teammate's subagents run in the foreground, and that asking for a background one "returns an error." With fork mode enabled (CLAUDE_CODE_FORK_SUBAGENT=1), a teammate's Agent-tool dispatch is instead silently launched async — Async agent launched successfully, no error, no warning. Because a teammate never receives task-notifications (its workers' completions route to the top-level session), the teammate then waits forever on a child it structurally cannot hear finish. The foreground-inline result was the only delivery mechanism a teammate had; fork mode removes it and substitutes a channel the teammate is deaf to.
With CLAUDE_CODE_FORK_SUBAGENT off (teams flag still on), the same dispatch runs foreground-inline exactly as documented and the stall disappears.
Note this is not gated on users setting the env var: the fork-mode resolver falls through to a staged-rollout gate when the variable is unset, so any agent-teams user can be opted into this behavior remotely. Setting CLAUDE_CODE_FORK_SUBAGENT=0 explicitly is currently the only way to pin it off.
The docs contradict each other
No background subagents from in-process teammates: an in-process teammate's own subagents run in the foreground. Asking for a background one, whether withrun_in_backgroundor a subagent definition that setsbackground: true, returns an error, because a teammate's background work can't outlive the lead's process.
sub-agents § Run subagents in foreground or background:
WhenCLAUDE_CODE_FORK_SUBAGENTis set to1, every subagent runs in the background and the frontmatterbackgroundfield has no effect, because fork mode removes therun_in_backgroundparameter from theAgenttool.
Neither page cross-references the other. In practice the fork rule wins, silently.
Repro
- Set
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1andCLAUDE_CODE_FORK_SUBAGENT=1; defaultin-processteammate mode. - Lead spawns a teammate.
- Teammate dispatches a subagent via the Agent tool (no
run_in_background, nobackground: truein the definition).
Expected (per agent-teams docs): foreground-inline run, result in the tool result; a background request would error.
Actual: Async agent launched successfully. The child's completion notification is delivered to the top-level session; the teammate is never woken and stalls indefinitely. Observed live across multiple dispatches (the teammate yielded to wait and no notification ever arrived; only a manual SendMessage from the lead recovered it).
Mechanism (static analysis of the 2.1.220 bundle)
The documented teammate guard exists and runs — there are two precondition checks (string anchor: subagent_teammate_background_denied) that throw exactly the documented error for run_in_background: true and for a definition with background: true. The defect is a coverage gap between the guard and the async decision that runs after it:
async = isRemote
|| ( runInBackgroundParam === true
|| agentDef.background === true
|| coordinatorMode
|| forkSubagentsEnabled // <-- the culprit
|| (!isTeammate && runInBackgroundParam !== false) // the ONLY disjunct that respects teammate-ness
) && !disableBackgroundTasks
The guard tests the request (run_in_background === true || def.background === true); the decision tests the environment. The fork disjunct (and the coordinator-mode one) force async for teammates without ever consulting teammate-ness, so a teammate dispatch that requests nothing — or even explicitly passes run_in_background: false — goes async with no error.
Aggravations found in the same code path:
run_in_background: falsedoes not help. The explicit-false escape lives only inside the(!isTeammate && …)disjunct, which the fork disjunct short-circuits past. The guard's own error message ("Use run_in_background=false for synchronous subagents") is therefore incorrect advice under fork mode: explicittrueerrors, explicitfalseand omitted both launch async.- The async branch's owner stamp cannot name a teammate. It computes
owner = deriveOwner(spawnerAgentId) ?? mainSessionId(), and the derivation fails twice for teammates (registry record keyed by task id rather than agent id, and typein_process_teammatefails thelocal_agentgate) — so the owner is stamped as the main session and the completion notification routes there. Either half alone would be survivable (async + correct owner would notify the teammate; foreground + wrong owner never emits a notification at all); together they produce a silent permanent stall. - Rollout exposure. The fork-mode resolver is: coordinator mode → off; env truthy → on; env explicitly falsy → off; non-interactive → off; else a statsig rollout gate. So the interaction can activate for teams users who never set the flag. Explicit
CLAUDE_CODE_FORK_SUBAGENT=0pins it off ahead of the rollout check. - Same shape, untested:
coordinatorModeandisolation: "remote"also skip the teammate guard (code-read inference; we only probed the fork flag). - A fork-agent permission deny does not switch the disjunct off — it reads the raw flag state, not fork-agent availability.
Suggested fix shapes
- Scope the fork/coordinator disjuncts to non-teammates (
&& !isTeammate), preserving the documented foreground rule — or have the guard test the same inputs the async decision does, so the documented error actually fires. - Alternatively, if async-from-teammates is intended under fork mode, stamp the spawning teammate as the owner and route its completion notification to it.
- Docs: cross-reference agent-teams § Limitations and sub-agents § fork mode so the precedence is stated; correct the guard error message's
run_in_background=falseadvice if the behavior stays.
Workarounds (verified against the code path)
CLAUDE_CODE_FORK_SUBAGENT=0(explicit falsy — also pins off the staged rollout) when teams are in use.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1collapses the whole term and restores foreground for everyone — but kills all background tasks session-wide.- No call-parameter workaround exists: with fork mode on, a teammate cannot obtain a foreground subagent by any Agent-tool input.
Related
- #77300 — Monitor/background-Bash notifications never wake an idle teammate (the only prior teammate notification-blindness report; this issue is the Agent-subagent + fork-flag composition of it)
- #69212 — subagent results route to root teammate instead of spawning teammate (implies teammates were getting async dispatches earlier; reports the misrouting half only)
- #74614 —
run_in_background: falseignored when the dispatch includes aname(a different hidden input silently forcing async, stranding the caller) - #75043 / #77950 / #81438 — completion notifications bypassing the spawning parent at subagent depth
- #73578 / #69691 / #62633 — undocumented async escalation family
- #69621 — teammate background-task lifetime docs gap