Agent-team teammate reports 'Spawned successfully' but never registers in mailbox (v2.1.123, macOS, tmux)
Summary
Agent-team teammates spawned via the Agent tool report Spawned successfully but the child Claude Code subprocess never registers with the parent's mailbox. current-team.json stays at members: [] and the orchestrator never receives a reply.
Environment
- macOS Darwin 24.4.0 (arm64)
- Claude Code v2.1.123 (native installer, binary at
~/.local/share/claude/versions/2.1.123) - tmux 3.6a (Homebrew)
- zsh
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1enabled- Reproduced on v2.1.118 and v2.1.123. Issue is independent of binary version.
Reproduction
- Orchestrator session (interactive
claude) calls:
````
Agent({
subagent_type: "general-purpose",
model: "sonnet",
mode: "acceptEdits",
team_name: "<team>",
name: "test-spawn",
prompt: "reply HELLO and end your turn"
})
- Tool returns
Spawned successfullywithagent_id: test-spawn@<team>. - Tmux pane shows the spawn shell command, optionally a
compdef: command not foundwarning from zsh init, thenResume this session with: claude --resume <uuid>, then a fresh shell prompt. ~/.claude/teams/<team>/config.jsoncontains the team butmembersis empty.- Orchestrator's
current-team.jsonshowsmembers: []. No teammate reply ever arrives. Orchestrator hangs or times out.
Expected behavior
Child process registers itself in the parent team's config.json members array, opens IPC mailbox, processes the prompt, and replies via SendMessage.
Actual behavior
Child exits cleanly (prints "Resume this session" — the standard exit message) before completing the IPC handshake. Orchestrator sees Spawned successfully but never gets a reply.
Diagnostic results
Direct binary tests work:
echo "hi" | /Users/<user>/.local/share/claude/versions/2.1.123 --print --model sonnet --permission-mode bypassPermissions
# returns "Hey."
env CLAUDECODE=1 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 \
/Users/<user>/.local/share/claude/versions/2.1.123 \
--print "reply HELLO" \
--agent-id t@td --agent-name t --team-name td --agent-color blue \
--parent-session-id <fresh-uuid> --agent-type general-purpose \
--permission-mode auto --model sonnet
# stdout: HELLO, exit 0
Manual tmux pane reproduction (no orchestrator) — child binary stays alive and shows banner:
tmux new-session -d -s test
tmux send-keys -t test "env CLAUDECODE=1 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 \
/Users/<user>/.local/share/claude/versions/2.1.123 \
--agent-id t@td --agent-name t --team-name td --agent-color blue \
--parent-session-id <fresh-uuid> --agent-type general-purpose \
--permission-mode auto --model sonnet" Enter
sleep 5
tmux capture-pane -t test -p
# Pane shows Claude Code banner, "@t" agent prompt, no errors, stays alive.
So the binary itself spawns correctly under tmux. The failure is internal to Claude Code's orchestrator-side spawn logic when invoked via the Agent tool with team_name.
Suspected cause
Orchestrator declares Spawned successfully based on tmux pane creation (or the binary returning a session id) rather than on the child completing the mailbox handshake. The child appears to start, then exits before subscribing to the mailbox — possibly because:
- The
--parent-session-idit receives doesn't match a parent that has begun listening on the corresponding mailbox path. - A race between team config write and child subscribe.
- The child's interactive-mode init aborts when its stdin is the tmux pane (different from manual reproduction stdin path).
Side-quest finding (already worked around)
Independent of this bug, ~/.zshrc lines that source process substitutions (source <(ng completion script)) without an [[ -o interactive ]] guard emit compdef: command not found warnings during shell init in tmux panes. After guarding zshrc, the warning is gone and a manual tmux spawn succeeds — but the orchestrator's Agent call still fails to receive a reply, which is why I believe the spawn-vs-handshake issue is a separate bug.
Workaround
Use plain subagents (Agent without team_name) — these spawn reliably. Forfeits team mailbox / per-teammate fresh-context-window benefits.
Repo for reference
The orchestrator system that hits this consistently: <https://github.com/borabekarr/claude-code-system> (private/local but happy to provide minimum repro on request).
3 Comments
This is lifecycle-registration failure between spawn acknowledgment and actual availability.
If a teammate reports “Spawned successfully” but never registers in the mailbox, then the system is asserting success at an intermediate step while the control surface the user depends on never becomes real.
Why it matters:
I would want clarity on:
spawn requested,runtime up, andmailbox registeredinstead of collapsing them into one premature success stateLifecycle status has to describe the state users can actually act on. If “spawned successfully” precedes mailbox visibility indefinitely, the control plane is overstating reality.
Update for everyone hitting the "teammate spawns then dies in 1–10 seconds" bug
Thanks to the reviewer who reframed this as a lifecycle-registration failure between spawn acknowledgment and actual availability. That framing was exactly right and turned out to point at a deeper problem than the original symptom. Posting the full investigation + fix here so anyone hitting the same thing can copy-paste their way out.
---
TL;DR
Two independent bugs were stacked on top of each other:
context-mode-router.py,read-discipline.pyin our repo) were readingCLAUDE_SESSION_IDandCLAUDE_AGENT_IDfromos.environ. Claude Code does not expose these as environment variables to PreToolUse hooks (upstream tracking: #40140, #16424). Both reads returned empty strings, all sessions keyed to"anon", offense state accumulated across every run forever. The first 300+-lineReadfrom a fresh teammate hard-denied because the file already had 8 strikes from prior sessions. The teammate had no recovery path and the harness ended the session within 3 seconds.Agent({team_name, name, ...})returns"Spawned successfully"on spawn-request acknowledgment only. There is no signal back to the orchestrator when a teammate dies between ack and the orchestrator's first follow-up message.members[].isActivestaystruein~/.claude/teams/<team>/config.jsonindefinitely, and any single-teammate enforcement hook then zombie-blocks every future spawn until manual cleanup.Bug 1 caused the suicides we were observing. Bug 2 explains why the suicides went undetected for minutes (in our case: 4 minutes between the teammate's death and our orchestrator's first follow-up).
---
Empirical evidence (one incident, one timeline)
Team
exec-do-you-think-we-misty-waffle-20260429-134713, memberstep-1-executor:| UTC timestamp | Event | Source |
|---|---|---|
| 13:49:04.321Z | Briefing delivered, marked
read=true|inboxes/step-1-executor.json|| 13:49:06.682Z | Teammate session jsonl first event |
~/.claude/projects/.../cb19bbd3-...jsonl|| 13:49:09.762Z | Last event in teammate jsonl — session ended | same file, line 22 of 22 |
| 13:53:16.114Z | Orchestrator's first
SendMessage(shutdown_request)to a long-dead PID | inbox file |Three-second teammate lifetime. Four minutes of orchestrator blindness. The teammate's last tool call was a single
Readon a 418-line file that the localcontext-mode-router.pydenied withSecond offense for pattern '...'— except it was the teammate's first Read in its first turn. The hook had been counting against the global"anon"session for weeks.---
Reviewer's three questions, answered empirically
Yes. Five distinct signals exist on different timelines:
| State | Source-of-truth signal |
|---|---|
|
spawn_requested|Agent({...})SDK call returned ok; member listed inconfig.json#members[]||
runtime_up|ps -axwwo pid,commandrow containsclaude/versions/and--agent-id <member>@<team>||
mailbox_ready| PID alive AND~/.claude/teams/<team>/inboxes/<member>.jsonexists and is writable ||
producing| Member jsonl growing at~/.claude/projects/.../{session-id}.jsonl||
dead| Inbox file or config entry exists, but no PID matches |The SDK collapses them into one positive
Spawned successfullyack.Neither. Identity and indexing are correct. In team mode each teammate gets:
agentIdof<member>@<team>,session_iddistinct from team-lead's,subagents/folder unlike old-style Agent-tool subagents),The teammate fully starts up. Then it dies seconds later from a hook deny / harness termination / unhandled tool-deny / OOM / anything, and the death never propagates back. Control plane stays at
members[].isActive: true.Not at the SDK layer today. We synthesized the distinction locally from filesystem and process state. Drop-in scripts below.
---
Fixes (drop-in for similar setups)
1. Patch your PreToolUse hooks to read identity from stdin JSON, not env
The
PreToolUsehook input on stdin includessession_id,transcript_path,cwd,tool_name,tool_input. It does NOT includeagent_id(upstream feature request: #40140).Replace any
os.environ.get("CLAUDE_SESSION_ID")/os.environ.get("CLAUDE_AGENT_ID")in your hooks with stdin parsing:Then key any per-session state files on
session_id(andagent_idif you have it), and reset state whensession_idchanges. Wipe your existing offense / state files once after deploying — they will be poisoned with cross-session counts.2. Add a lifecycle probe + reconciliation poller
Two short scripts. Both read-only, idempotent, sub-second.
teammate-lifecycle.sh— single-shot probe, prints one line of JSON:post-spawn-reconcile.sh— polls every 1s up to a timeout. Exit 0 =mailbox_ready. Exit 1 =deadormissing. Exit 2 = timeout.Call the poller in your orchestrator immediately after every team-mode
Agent({...})returns. Treat exit 1 asspawn_self_died(retry), exit 2 asspawn_no_mailbox(retry), three consecutive lifecycle failures as terminal debt.3. Patch any single-teammate enforcement hook to skip dead members
If you have a PreToolUse hook on
Agentthat blocks new spawns while an existing member is "active" (we do —single-teammate.py), it will zombie-block forever once a teammate self-dies. Add a PID liveness check using the same--agent-idawk needle, and treat config members whose PID is gone as effectively shutdown with a stderr warning. Onepsinvocation, reused across all members. Fail-open on every exception.---
Verified end-to-end
After shipping all three patches, ran a real-world smoke test:
| Phase | Check | Result |
|---|---|---|
| Spawn |
Agent({team_name, mode: "acceptEdits", subagent_type: "general-purpose"})| ack received || Reconcile |
post-spawn-reconcile.sh <member>@<team> 15| exit 0 in 0s,state=mailbox_ready, PID matched || Real work | Teammate completed a
Writeto disk | file appeared at T+1s, exact 487 bytes || Single-teammate hook (alive) | New spawn attempted while teammate's PID alive | exit 2, blocks correctly |
| Graceful shutdown |
SendMessage(shutdown_request)+teardown-teammate.sh|PID=N TERM okwithin 3s grace, no SIGKILL needed || Single-teammate hook (dead) | New spawn attempted while teammate's PID gone but config still says
state: "active"| exit 0 with stderrsingle-teammate: auto-skipping dead member ... PID gone|| Lifecycle probe (post-death) |
teammate-lifecycle.sh|state=dead, PID null, inbox file retained for evidence || Reconcile (post-death) | poller | exit 1 in 0s — would correctly trigger
spawn_self_diedretry |Hours of teammate suicide pain → reproducible, observable, recoverable lifecycle.
---
To the reviewer
The framing — "Lifecycle status has to describe the state users can actually act on. If 'spawned successfully' precedes mailbox visibility indefinitely, the control plane is overstating reality" — was the load-bearing insight. The env-var hook bug was the proximate cause of our specific incident, but it was your point that pushed us to add structural safety so the next failure mode (whatever it is) is detected within seconds instead of minutes. Appreciate it.
For upstream improvement, the two open issues most directly relevant are #40140 (PreToolUse needs
agent_id) and #16424 (subagentsession_idshared with parent). A standardized lifecycle event stream —spawn_requested/runtime_up/mailbox_ready/producing/deadas first-class signals — would let every downstream toolchain stop reinventing the synthesis above.This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.