SendMessage to: "main" resolves to the root session, so a nested subagent's report bypasses the agent that spawned it
What happens
When a subagent spawns its own subagents, a leg that reports withSendMessage({to: "main", ...}) has that message delivered to the root
session, not to the agent that dispatched it. The dispatching orchestrator
never receives the report and has no way to wait for one.
"main" is the documented default recipient and the one models reach for by
default, so this is the common path rather than a corner case.
Minimal repro
- In a session, spawn agent A with the Agent tool.
- Have A spawn agent B, instructing B to report its result back.
- Have B finish with
SendMessage({to: "main", message: "<result>"}).
Observed: the message appears in the root session. A sees nothing and
finalizes without B's result.
Expected: A receives it, since A is B's caller.
The same shape at depth 3 has a sharper edge: an agent spawned by an unnamed
subagent has no addressable handle for its own caller at all. In our logs a
general-purpose agent tried to: "<its parent's agent type>", was told the
recipient was unreachable by name, and fell back to the root session.
Impact
We run three-model code review as an orchestrator subagent that fans out to
per-model review legs. Every round in one session had its Claude leg's findings
land in the root session instead of the orchestrator. In the worst instance the
leg had found a live defect; its orchestrator finalized the round without those
findings and returned an approving summary. Only a human relaying messages by
hand kept the bad change from merging.
Subagents have started designing around it. One orchestrator wrote in its own
summary: *"The Claude leg never delivered its report to me — the known
nested-routing misdelivery; if it surfaces in main, please relay it."*
Suggested resolution
Resolve "main" relative to the spawner: for an agent at spawn depth n,to: "main" should address its immediate parent, and only address the root
session for a depth-0 agent. That matches what "report to main" means from
inside a nested dispatch and needs no change to any existing prompt.
If changing the meaning of "main" is too disruptive, two smaller changes would
each be enough on their own:
- Give every agent a stable, addressable handle for its caller — e.g. a
documented to: "parent" alias, or surfacing parentAgentId (already
present in <session>/subagents/agent-<id>.meta.json) in the agent's own
context so it can address the parent explicitly.
- Make an unnamed agent addressable by its agent id, so the depth-3 case has
some working handle rather than falling back to the root session.
Workaround we shipped
A SubagentStop / TeammateIdle hook that resolves the caller fromagent-<id>.meta.json (parentAgentId → that parent's name), credits
delivery only when a SendMessage in the final turn went to that caller, and
otherwise blocks the stop once with an instruction naming the right recipient.
It cannot cover the unnamed-parent case, which is why we are filing this.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗