[Bug] SendMessage(to: "main") from background subagent silently dropped instead of reaching spawner

Status Open
Reported on v2.1.205
Maintainer reply None cached
Activity 4 comments · opened Jul 10, 2026

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

[]

View original on GitHub ↗

4 Comments

kcarriedo · 1 month ago

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.

lazarillo · 1 month ago

Yeah, to: "spawner", or some similar verbiage sounds like a simple, elegant solution.

IgorGanapolsky · 1 month ago

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":

  1. Either deliver to the spawner session and ack, or return a hard error from SendMessage.
  2. Idle/completion notifications should include undelivered outbound messages so operators can see the drop.
  3. Callers need a timeout path: if main never acks, surface that in the subagent turn instead of completing as success.

Until fixed, I treat to: \"main\" as unreliable and write status to a shared file the parent polls — ugly, but it fails closed.

lazarillo · 23 days ago

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 the claude agents TUI, 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 via SendMessage(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.md telling 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 my CLAUDE.md, since it is simply an error that needs to be fixed.