[BUG] Named fork (`subagent_type: "fork"` + `name`) silently inherits zero context in interactive sessions

Open 💬 0 comments Opened Jul 9, 2026 by seeekr

TL;DR

Docs say a fork subagent "inherits the entire conversation so far." In an interactive session, calling the Agent tool with subagent_type: "fork" plus a name spawns an agent with zero inherited conversation — no error, no warning; it answers confidently from empty context. The identical call without name inherits fully (verified back-to-back in the same session, 3 failures / 1 success). In clean headless (claude -p) runs the named variant inherits correctly, so the loss is conditional on the interactive/agent-teams environment — but there it is fully reproducible and silent, which makes fork results quietly wrong for anyone who names their forks (the Agent tool docstring itself encourages naming for SendMessage addressability).

Suggested fix: thread the parent transcript for named forks too, or reject name on forks with an explicit error. Silent empty context is the worst of the options.

Details (full repro + analysis)

Environment: claude-code 2.1.205 (darwin-arm64, bun global install); headless tests also on 2.1.204. macOS 15 (Darwin 25.5.0). Relevant user settings: teammateMode: "in-process", defaultMode: "bypassPermissions". In the interactive session the fork type was available via staged rollout (no CLAUDE_CODE_FORK_SUBAGENT in launch env); headless tests gated it explicitly via the env var.

Observed matrix:

| Spawn | Environment | Result |
|---|---|---|
| subagent_type:"fork" + name | interactive session | empty context — 3/3 (two spawned mid-turn, one immediately after a completed+flushed turn) |
| subagent_type:"fork", no name | same interactive session | full inheritance (quoted the live in-flight user message verbatim; enumerated all prior turns incl. teammate-message deliveries) |
| subagent_type:"fork" + name | clean claude -p, CLAUDE_CODE_FORK_SUBAGENT=1 | full inheritance (fork quoted planted codeword) |
| subagent_type:"fork", no name | clean claude -p, CLAUDE_CODE_FORK_SUBAGENT=1 | full inheritance; fork's first API call shows cache_read_input_tokens == parent's cache_creation_input_tokens (39,186) — shared prompt cache working as documented |
| subagent_type:"fork" | clean claude -p, no env var | Agent type 'fork' not found (expected gate) |

Interactive repro (the failing case):

  1. Session with several turns of history; fork type available (staged rollout), teammateMode: "in-process".
  2. Call Agent tool: {subagent_type: "fork", name: "probe", prompt: "Quote the earliest user message you can see; count user-role turns before this probe."}.
  3. Spawn result reads "The agent is now running and will receive instructions via mailbox" (teammate wording; the working nameless spawn instead reads "Async agent launched successfully" with an output_file).
  4. The named fork reports zero user-role turns, no prior assistant turns — only the standard fresh-session bootstrap (CLAUDE.md, env/git snapshot) plus the probe itself. Its transcript jsonl starts at the probe with parentUuid: null, and no fork-context-ref record is written anywhere (parent session jsonl grepped).
  5. Repeat without name: full inheritance, same session, immediately after.

Static analysis (strings/decompile of the shipped 2.1.205 binary; minified names, flagged as inference where applicable): every spawn path funnels through one shared generator (internal nj) that builds the model-facing array as [...sanitize(forkContextMessages), ...taskPrompt] — if the caller doesn't pass forkContextMessages, the agent silently gets zero history. The /fork slash-command path (Pxs / spawnForkFromDirective) unconditionally passes the live parent array (t.messages). A structurally different caller — the agent-teams in-process teammate runner (hgy, logs as [inProcessRunner]) — reuses the same forkContextMessages parameter for the teammate's own resume history, which is empty on fresh spawn. The observed mailbox wording on the failing spawns suggests named forks in interactive/teams-active sessions route through that runner, so the parent transcript is never threaded and the fork-context-ref recorder never fires. The exact routing conditional (why interactive+named routes to the teammate runner while headless+named does not) could not be isolated by static reading — likely keyed on agent-teams/mailbox machinery being active rather than on name per se.

Impact: any workflow that names forks for SendMessage continuation gets clean-context agents that believe they are forks and answer accordingly — silent wrong results, no failure signal.

---
_This issue was researched and written by Claude Code (Fable 5) investigating its own fork behavior, and filed on behalf of @seeekr._

View original on GitHub ↗