[Bug] SendMessage(to: "main") from background subagent silently dropped instead of reaching spawner
Bug Description
▎ SendMessage(to: "main") from a background subagent is silently dropped instead of reaching the spawning session.
▎
▎ The SendMessage tool documents to: "main" as the way a background subagent reaches "the main conversation." In practice the spawner never receives it — the subagent completes, emits an idle_notification, and its content is lost with no error. The mechanism appears to be that "main" resolves relative to the caller, so in a subagent's runtime it addresses itself.
▎
▎ Repro (clean config, no custom hooks): from a main session, spawn a background Agent-tool subagent whose only action is SendMessage(to:"main","PING") and end; the main session never receives "PING".
▎
▎ Expected: the message reaches the spawning conversation (per the tool's to docs).
▎ Impact: silent data loss in multi-agent / dynamic-workflow runs — a subagent's entire (possibly expensive) output can vanish. Forces file-write / transcript-scraping workarounds.
▎ Confidence: message-non-delivery directly observed multiple times (2026-07-02 and 2026-07-10); the exact "resolves to self" mechanism is inferred. A clean repro on our machine is currently masked by a PreToolUse hook we installed to block to:"main" because of this bug.
▎ Suggested fix: make to:"main" resolve to the spawner for subagents (match the docs), or fail loudly (return a delivery error) and correct the tool documentation.
Environment Info
- Platform: darwin
- Terminal: xterm-256color
- Version: 2.1.205
- Feedback ID: 58effc07-8344-49b3-88f3-f26179dd0bff
Errors
[]
4 Comments
Hit this exact behavior building a background-agent coordination layer. The
to: "main"routing loop is the core issue - in a subagent runtime, "main" resolves to the subagent's own session, so the SendMessage call disappears silently and the orchestrator never knows the subagent finished or what it found.Current workaround: instead of SendMessage(to:"main"), background subagents write a structured result file to a known path, and the spawner polls that path via a short-sleep loop. It works but it forces the orchestrator to do active polling rather than being event-driven, which burns tokens and adds latency.
A minimal fix that would unblock a lot of background-agent patterns: make
to: "spawner"a reserved keyword that always routes to the session that issued the Agent tool call, resolved at spawn time rather than call time. The spawner session ID is known at that point and could be bound into the subagent's context as a constant.Yeah,
to: "spawner", or some similar verbiage sounds like a simple, elegant solution.If
SendMessage(to: \"main\")is documented as the way a background subagent reaches the spawner, silent drop is a contract failure — the subagent thinks it reported; the parent never hears it.For multi-agent reliability this is the same class as "fire-and-forget with no ack":
Until fixed, I treat
to: \"main\"as unreliable and write status to a shared file the parent polls — ugly, but it fails closed.Sure. I see your point, @IgorGanapolsky . But unfortunately,
SendMessage(to: "main")is what the agents use by default. Ie, if I am using Claude Code in theclaude agentsTUI, and I have an ongoing chat, when Claude launches a sub-agent, the sub-agent will assume that it should send a message to "main" because the agent that launched the sub-agent sees itself as "main", and so it tells the sub-agent to communicate with it viaSendMessage(to: "main").In my case, that is exactly what happened. I was not spawning that sub-agent myself. Claude was spawning it. I could give suggestions to Claude by adding something in the
CLAUDE.mdtelling it not to do that, but as we know, those suggestions are not always listened to. And even if they are, it isn't something I should need to put in myCLAUDE.md, since it is simply an error that needs to be fixed.