Team messaging: lead session resumed after restart never delivers teammate messages (InboxPoller short-circuits on missing teamContext)

Resolved 💬 1 comment Opened Apr 28, 2026 by marcoabreu Closed May 30, 2026

Summary

With CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1, when a team-lead session is restarted (process crash, machine restart, new CLI session opened in the same project), the new lead session never receives messages from teammates, while the lead → teammate direction continues to work normally.

The on-disk inbox file accumulates teammate messages with read:false indefinitely; they are never delivered as <teammate_message> turns to the lead.

Reproduction

  1. Start a CLI session, run TeamCreate to create team T.
  2. Spawn one teammate via Task with team_name=T.
  3. Confirm round-trip: lead SendMessage to teammate works, teammate reply is delivered to the lead session as a <teammate_message> turn.
  4. Quit the lead CLI session (or kill the process / reboot).
  5. Start a new CLI session in the same working directory. The team file at ~/.claude/teams/T/config.json is still present.
  6. From a teammate session (or by writing directly to ~/.claude/teams/T/inboxes/team-lead.json with the documented schema), send a new message to the lead.

Expected: the new lead session receives the message as a <teammate_message> turn within ~1s (the InboxPoller cadence).

Actual: the message lands in ~/.claude/teams/T/inboxes/team-lead.json with read:false and stays there forever. The lead session never sees it. Manually flipping read:true does nothing for the lead session — the message is never surfaced as a turn at any point.

The bug persists across CLI restarts and machine reboots. Only direction affected: teammate → lead. Lead → teammate works because teammate sessions are spawned fresh and get their teamContext populated normally.

Root cause (from binary inspection)

The InboxPoller runs at 1 Hz in every agent session and gates delivery on:

if (!H) return;        // H = teamContext present in app state
if (_ || q) return;    // session-busy flags
if (!Aj6(state)) return;

teamContext (containing teamName, teamFilePath, leadAgentId, teammates) is set in app state only by the TeamCreate tool's reducer:

setAppState(s => ({ ...s, teamContext: { teamName, teamFilePath, leadAgentId, teammates: { ... } } }))

There appears to be no code path that re-hydrates teamContext from the on-disk team config file when a session starts up and finds itself listed as leadAgentId of an existing team. As a result:

  • A freshly spawned teammate session gets teamContext set during team join → its poller runs.
  • The original lead session that called TeamCreate had teamContext set → its poller ran.
  • A new lead session that resumes work on an existing team starts with teamContext === undefined → the poller's first guard if (!H) return is true on every tick → no inbox is ever read for the lead.

The team config file's leadSessionId is also written once at TeamCreate time and is never refreshed when a new session adopts the lead role, which is consistent with the rehydration path being missing entirely rather than failing.

Impact

For any workflow that relies on long-running teams across multiple CLI sessions (multi-agent orchestration, swarms with state larger than one session), this silently breaks bidirectional messaging after the first crash or restart. The lead can keep dispatching work, but cannot observe completion, replies, idle notifications, plan-approval responses, shutdown acknowledgments, or any other message type — all of which flow through the same inbox.

The only operational workarounds today are:

  1. Always TeamCreate a fresh team in the current session and never reuse a team across sessions (loses team memory and context).
  2. Manually parse the inbox JSON file out-of-band and inject contents back into the lead session by hand each turn.

Both are significant overhead for a feature flag whose entire premise is durable multi-agent coordination.

Suggested fix

On session startup, if a team config file lists this session's agent as the lead (or if the user explicitly opts in to adopting an existing team), hydrate teamContext in app state with the on-disk team data and update leadSessionId to the current session ID. The InboxPoller should then proceed to read and deliver pending messages on the next tick.

A user-facing diagnostic when the file exists but teamContext is null would also help — currently the failure is silent (no error, no log surfaced to the user; only the internal [InboxPoller] debug log would show that polling never starts).

Environment

  • Claude Code 2.1.121 (and at least 2.1.119, 2.1.120 — confirmed across multiple builds)
  • CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
  • macOS, native binary

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗