Agent tool `name` parameter silently switches to teammate protocol, losing background agent results
Bug Description
When the Agent tool is called with a name parameter in a session that has (or has had) a team configuration, the spawn silently takes the teammate path instead of the regular background agent path. This causes the agent's results to be lost — the calling session never receives a task-notification with the work output.
Reproduction Steps
- Start a background session (
--agent claude) - Use the Agent tool without
name— observe it returns "Async agent launched successfully" and eventually delivers results viatask-notification✅ - Use the Agent tool with
nameparameter (e.g.,name: "my-agent") — observe it returns:
````
Spawned successfully.
agent_id: my-agent@session-<id>
name: my-agent
The agent is now running and will receive instructions via mailbox.
- The named agent completes its work but sends
idle_notificationthrough the team inbox instead of atask-notification - The calling session never receives the agent's results
Expected Behavior
The name parameter should only provide addressability (for SendMessage({to: name})), without changing the spawn path or result delivery mechanism. A background agent spawned with name should still deliver results via task-notification.
Actual Behavior
nameparameter triggers teammate spawn path ("status": "teammate_spawned")- Agent is registered in
~/.claude/teams/session-<id>/config.jsonas a team member - Agent uses inbox-based
idle_notificationprotocol instead oftask-notification - Job state tracker records the agent in
fanarray but never receives completion signal → zombie entries shutdown_requestsent to the teammate does not terminate it — it continues sendingidle_notification
Evidence from Real Session
Session 0287d9cb (Claude Code v2.1.185) experienced this:
| Condition | Spawn Response | Result Delivery | Outcome |
|-----------|---------------|-----------------|---------|
| No name param | "Async agent launched successfully" | task-notification, 3.8s | ✅ Result received |
| With name param | "Spawned successfully...via mailbox" | idle_notification | ❌ Result lost |
The session's own thinking confirmed the diagnosis:
"The subagents sent idle notifications but didn't report their findings. They seem to be using a team protocol that doesn't match the Agent tool's behavior."
Impact
- Agent work results are silently lost (no error surfaced to user)
- ~20 minutes wasted per occurrence (waiting + redoing work)
idle_notificationmessages pollute context window (~200 tokens each, recurring)- Job state
fanarray accumulates zombie entries that never clear shutdown_requestdoesn't terminate the zombie teammates
Environment
- Claude Code v2.1.185
- macOS (Darwin 23.2.0, arm64)
- Background session mode (
--agent claude)
Suggested Fix
Either:
- Don't let
namechange the spawn path — always use the background agent protocol regardless ofname;nameshould only add SendMessage addressability - Or clearly document and error — if
nametriggers team mode, surface this as an explicit choice rather than a silent protocol switch
Showing cached comments. Read the full discussion on GitHub ↗
8 Comments
Your root-cause analysis looks right — the
nameparameter flips the spawn onto the teammate path, so completion arrives as anidle_notificationthrough the team inbox instead of atask-notificationto the caller. One thing worth adding, because it changes the severity: the results aren't actually lost — they're persisted to disk, and they're also still retrievable live.The work is on disk. Each spawned agent persists its full turn-by-turn output to:
I verified this layout first-hand on my own install (157
subagents/directories, 440agent-*.jsonlfiles, each holding the complete agent transcript —message,agentId,timestampper line). Theagent_id: my-agent@session-<id>your spawn returned maps to one of these files. So even though the calling session never got atask-notification, the named agent's actual output is sitting in…/subagents/agent-<id>.jsonland can be read back:That turns a silent data-loss bug into a recoverable misrouting bug — useful both for anyone hitting this now and for triage severity.
It's also retrievable live. Because
nameregistered the agent as a teammate (teammate_spawned, in~/.claude/teams/session-<id>/config.json), it's addressable. Since it completed and sentidle_notification, it's idle-waiting in the inbox rather than gone —SendMessage({to: "my-agent"})asking it to report its result should pull the output back through the team inbox. (That part is reasoned from the protocol you described, not verified on my end — but it follows from the agent being alive and addressable.)Workaround: omit
namefor background agents whose results you want delivered to the caller viatask-notification; only passnamewhen you actually want the teammate/mailbox addressability. The bug is that those two concerns are coupled — as you said,nameshould add addressability without changing the spawn path or the delivery mechanism.For the maintainers: the
fan-array zombie entry never getting a completion signal is the same coupling — anamed background agent should still resolve its job-state row viatask-notification, and addressability (SendMessage({to})) should be orthogonal to delivery. The data is preserved on disk either way, so this is a routing/notification bug, not loss.(Not affiliated with Anthropic — the
subagents/agent-*.jsonlpersistence path is verified on my own install; the liveSendMessageretrieval is inferred from the protocol you documented.)This is a sharp edge. The
nameparameter looks like a display/addressability hint but it's actually a spawn-path switch -- nothing in the Agent tool signature signals that distinction. The silent switch to teammate protocol means the bug only surfaces when you go looking for results that never arrived.The practical consequence in multi-agent orchestration: if you add
nameto improve observability (so you can address agents by name withSendMessage), you lose result delivery viatask-notification. You can't have both in the current model, so you end up choosing between addressability and reliable result receipt.Workaround while this is unresolved: avoid the
nameparameter and instead track agent identity via the session ID returned from the background spawn. If you need SendMessage addressability, register the session ID in a shared file (e.g.,.claude/agents/registry.json) at spawn time and look it up by task name when you need to address it directly. Clunky but avoids the spawn-path switch.The fix is a clear separation between
name(addressability label, no spawn-path side effect) and something liketeammate: true(explicit opt-in to the teammate protocol). The current implicit detection based on parameter presence is too easy to trigger accidentally.Is the silent path switch observable in the Agent View / Workflows right pane? Seeing which protocol was used at spawn would at least make the failure mode diagnosable without reading the output carefully.
Observability answer: No, the protocol switch is NOT visible in Agent View
Per the official docs, subagents and teammates spawned within a session don't appear as separate rows in Agent View. The Agent View row for the parent session shows a
done/totalcount for background subagents, but doesn't distinguish spawn protocol.The teammate "agent panel" (below the prompt input) shows teammate names and status, but doesn't indicate whether a given agent was spawned as a regular subagent vs. a teammate at the protocol level.
The only observable signal is the tool result text in the conversation transcript:
"Async agent launched successfully."+agentId: a64f71c83af7a33ad"Spawned successfully.\nagent_id: my-agent@session-<id>\nThe agent is now running and will receive instructions via mailbox."These messages scroll by in the transcript and are easy to miss — especially in multi-agent orchestration where many agents are spawned in rapid succession.
Worse than expected:
nametriggers team auto-creationIn the original investigation I assumed the team protocol only activated when a team already existed. Further PoC testing reveals it's worse: using
namecreates a team config from scratch if none exists.In a fresh background session (no prior team), calling
Agent({name: "test-named-agent", run_in_background: true, ...}):~/.claude/teams/session-<id>/config.jsonwith ateam-leadentry"teammate_spawned"statusidle_notificationinstead of delivering results viatask-notificationThis means the trigger condition is simply
nameparameter present — not "session has existing team." Any attempt to usenamefor observability in any session will silently activate the teammate path.Shutdown is also broken
After the teammate completes its work and enters idle state,
SendMessage({to: "agent-name", message: {type: "shutdown_request", ...}})does not terminate it. The teammate responds with anotheridle_notificationand continues running. In the investigated session:shutdown_requestsent at 23:13idle_notificationstill arriving at 23:31, 23:41, and beyondThe zombie teammates accumulate in the job state
fanarray (never cleared) and keep polluting the parent's context window with periodicidle_notificationmessages (~200 tokens each).Workaround confirmation
Your suggested workaround (avoid
name, use the returnedagentIdfor tracking) is confirmed working. In our PoC:| Approach | Spawn Response | Result Delivery | Works? |
|----------|---------------|-----------------|--------|
| No
name| agentIda64f71c83af7a33ad|task-notification, 3.8s | ✅ || With
name|test-named-agent@session-<id>|idle_notificationonly | ❌ |The
agentIdreturned from the nameless spawn is usable withSendMessage({to: agentId})to continue the agent — so addressability isn't fully lost, just less ergonomic (opaque ID vs. human-readable name).+1 on the fix direction
Strongly agree with separating
name(display label / addressability) from an explicitteammate: trueopt-in for the team protocol. The current implicit detection via parameter presence violates the principle of least surprise — a display hint shouldn't change execution semantics.Still present in v2.1.206, and I can add three data points the original report didn't cover.
1. It is not limited to background sessions (
--agent claude)I hit this in a plain interactive CLI session. The repro doesn't need a background session or a pre-existing team config — the first named spawn creates
~/.claude/teams/session-<id>/config.jsonand registers the agent as a member.2. Controlled A/B in a single session —
nameis the only variableSame session, same
subagent_type: "general-purpose", samerun_in_background: false. Only difference is the presence ofname:|
name| Spawn response | Delivery | Wall clock ||---|---|---|---|
| omitted | (normal) | ✅ result returned inline | 5.5s |
|
"image-audit"|Spawned successfully… via mailbox| ❌idle_notification, body empty | never |Five named agents across this session (
reverse-trace,omission-audit,semantic-audit,file-audit,image-audit) — all five lost their results. One unnamed control agent — returned fine.3. The named agents do run. Only the return path is broken.
This matters for triage: it isn't a scheduling or startup failure.
image-auditleft 6 Python scripts and 4 unpacked directories in its scratchpad before going idle. The work completed; the result never propagated.Corollary: the results are unrecoverable through the tool. I sent
SendMessageto two of the idle named agents asking them to restate their findings — both responded with another emptyidle_notification. The only workaround I found was to instruct the agent to write its report to a file and read it myself, bypassing the return path entirely.4.
Workflowis unaffected — same session, same modelsThe
Workflowtool spawns its agents without aname, and in the same session it returned structured results from 12 agents with no losses. That's a useful control: the subagent execution environment is fine, theAgent-tool-with-namedelivery path is not.Suggestion
Given #74614 (
run_in_background: falsealso ignored whennameis present), both symptoms look like the same branch: presence ofnamediverts the spawn to the teammate path and everything downstream of it (delivery mechanism, sync/async honoring) follows the teammate contract instead of the Agent-tool contract.If decoupling is non-trivial, an interim mitigation that would have saved me ~40 minutes: warn at spawn time when
nameis passed, e.g.note: named agents deliver via team inbox, not via task-notification. Silent divergence is the expensive part.Environment
--agentflag)Correction / refinement to my previous comment — I ran one more experiment and the broken surface is narrower than I implied.
SendMessagefrom the named agent tomainworks. I instructed the idle named agent to callSendMessage({to: "main", ...})explicitly, and the message arrived in the parent session intact:So the agent→parent path is not dead in general. What is dead is specifically the final message / return value.
This also explains a failure mode I misread earlier. When I sent a named agent a
SendMessageasking it to restate its findings, it answered — but it answered by ending its turn, which routes the answer into the final-message path, which is the black hole. From the parent's side that is indistinguishable from "it ignored me and went idle again." The agent believed it had delivered. In its own words, after the channel test:It had not. Nothing arrived.
Updated picture:
| Path | Named agent | Unnamed agent |
|---|---|---|
| Final message / return value | ❌ lost | ✅ delivered |
|
SendMessage({to: "main"})| ✅ delivered | n/a ||
idle_notification| ✅ delivered (empty body) | n/a |Practical consequences for anyone hitting this:
SendMessage({to: "main"})explicitly. Asking it to "report back" is not enough; it will use the final-message path and silently lose the payload.If the teammate path must stay, the minimal fix might be to route a completing teammate's final message through the same inbox mechanism that
SendMessagealready uses successfully — the transport clearly works.One more finding, and I think it's the sharpest edge of this bug.
The harness actively steers the named agent into the black hole.
I told the stuck named agent to write its report to a file so I could read it directly, bypassing the dead return path. Its
Writecall was rejected by the harness with:So the harness instructs the agent to deliver via its final response — which, for a named agent, is exactly the path that discards the payload. The two behaviours compose into a guaranteed loss:
namediverts the spawn to the teammate path → final message is droppedidle_notificationThe agent's own words after I confirmed nothing had arrived:
It had complied with the harness, and the harness threw the result away.
(Credit where due: the agent refused to route around the
Writedenial with aBashheredoc, which is the correct behaviour. It then found the one legal channel that works —SendMessage({to: "main"})— and delivered a 6000-word report through it. That was the only reason ~20 minutes of real work survived.)Suggested triage priority: the
Writedenial message is safe and sensible for unnamed subagents, where the final response does reach the caller. For named/teammate agents it is actively harmful advice. Either exempt them, or fix the delivery path so the advice becomes true again.This is the kind of bug where the API surface needs to expose the protocol boundary as data, not prose.
Right now
namelooks like metadata, but it changes at least four contracts at once:task-notificationvs final-message / inbox semantics;The expensive part is that the agent can believe it delivered successfully while the parent receives only an idle state. That means neither side has a reliable shared receipt for the handoff.
A small invariant might make this mechanically testable: every Agent spawn returns an explicit delivery contract before work starts, for example:
Then
namecan be tested as addressability only. If it changesprotocolorresult_route, that should be an explicit opt-in, or at minimum a warning with a different return shape. The harness advice also becomes route-aware: "return findings as text" is safe only when the declared final-message route reaches the parent.This is the same boundary AgentFolio/SATP cares about at the reputation layer: not the private transcript, but whether a delegated agent produced verifiable receipts for who spawned it, where the result was supposed to go, and whether the terminal outcome actually reached that route. Without that receipt, the parent cannot distinguish "agent failed" from "runtime dropped the result."
Still reproduces on 2.1.220 (macOS, darwin 25.5.0), and this issue's diagnosis matches what I observed.
Data point from one session: ~10
Agentdispatches, all with anameparameter, in a controller session that dispatched implementer and reviewer subagents sequentially. 4 of them completed their work but never delivered a result — the controller received onlyidle_notificationwithidleReason: "available". In every case the subagent had genuinely done the work (files written, commits made); only the final report was lost.Sending the agent a
SendMessageasking it to relay its result recovered the output every time, which matches the workaround described in #70641.Two things that may help narrow it:
run_in_background: falsedoes not make a named dispatch synchronous. The spawn returnsThe agent is now running and will receive instructions via mailboxregardless, which is consistent with this issue's claim thatnameswitches the spawn to the teammate path — the background/foreground flag appears to be ignored once that path is taken.On the workaround's cost: dropping
namerestores result delivery, butnameis what makes an agent addressable viaSendMessage({to: name}). In a review/fix loop that matters, because resuming the original implementer preserves its context, whereas a fresh unnamed agent has to rebuild it. So the choice is currently between reliable result delivery and resumable agents, and orchestration patterns that need both have no good option.Happy to provide more detail if useful.