Cross-session send_message silently lost after worktree recycle (reports sent, never delivered)

Status Open
Maintainer reply None cached
Activity 3 comments · opened Aug 13, 2026

Summary

Cross-session messages (mcp__ccd_session_mgmt__send_message) sent to a session whose worktree was recycled are silently lost: the sender gets a success result ("Message sent" / "Message queued"), but the recipient session never receives the message. After the recycle, even messages addressed to what appears to be the correct successor session are lost.

Environment

  • Claude Code (desktop app harness with CCD session management MCP), Windows 11 (win32)
  • Long-running multi-session project: several concurrent local sessions coordinating via send_message / list_events

Steps to reproduce (as observed over several days)

  1. Session A (long-lived "coordinator" session) runs in a git worktree. The harness recycles the worktree mid-conversation ("The git worktree ... was recycled. This session now operates on a fresh worktree ...", same branch, new path). The session continues normally.
  2. Session B calls send_message targeting Session A by its sessionId (obtained earlier from list_sessions or from a previously received message's "from" attribution).
  3. Session B receives a success result: Message sent to session ... (or Message queued for session ...).
  4. Session A never receives the message — no <cross-session-message> block ever appears in its context on any subsequent user turn.

Observed 4 independent losses over 3 days, from two different sender sessions. One loss occurred even when the sender explicitly replied to a freshly received message from Session A (i.e. addressing the "from" session), so the stale-id explanation may not cover all cases.

Additional observation: the pre-recycle session still appears in list_sessions output of other sessions (with its old branch/PR metadata), so senders keep resolving the dead id as a valid target.

Expected

Either:

  • delivery to the successor session (the recycle keeps the conversation alive, so logically the recipient still exists), or
  • an explicit error to the sender ("session not found / recycled"), so the sender knows to re-resolve the target.

Silent success with no delivery is the worst of both: both sides believe the message went through, and coordination silently breaks (in our case, two agent "strategist" sessions lost three days of hand-off packages before noticing).

Workaround we use

Recipients periodically read correspondents' transcripts via list_events instead of relying on delivery; senders treat >1 day of silence after a substantive message as a probable recycle.

View original on GitHub ↗

3 Comments

arthurmoraesfernandes-afk · 17 days ago

The worktree-recycle trigger here lands in the same pipeline — #86298 documents the same failure chain with root-cause analysis (Windows desktop, regression at app 1.28929.0 / bundled CLI 2.1.227, still present on app 1.30096.1.0 / CLI 2.1.229): the desktop bridge sends the message with a class-less origin:{kind:'peer'}, the recipient CLI/SDK's cross-session consent gate holds it for an approval no desktop surface offers, and dialogExpiry (~5 min) then drops it — while the sender keeps a success receipt. A memory-dump repro in that thread shows the payload reaching the recipient CLI process but never its transcript. Setting crossSessionInbound: "accept" in user settings does not unblock it (tested on running, cold-started, and post-restart fresh sessions).

Alley007 · 15 days ago

Adding a data point from a different environment (macOS desktop app, local sessions all sharing one project directory — not a git-worktree setup, so the recycle trigger described above doesn't apply here).

Same symptom, different/no trigger: over one working day, 5 independent peer sessions (in a coordinator + ~15-peer fleet) could reliably receive send_message from the coordinator, but their own replies back to the coordinator silently never arrived — one-directional, not a full bidirectional loss. The coordinator only recovered the real content by reading each peer's transcript directly (list_events) instead of relying on delivery — same workaround already described in this issue.

Possibly useful new data point — respawn does not fix it: one affected session was archived and replaced with a brand-new session (fresh conversation, no prior history, new sessionId). Its first message to the coordinator (an identification ping) delivered successfully; every message after that reverted to the same one-directional failure within minutes. So whatever's wrong isn't tied to an individual session's age/state/history — a session that was never "recycled" at all (it never existed before) hit the identical failure almost immediately. That seems relevant to narrowing down the root cause regardless of platform.

One affected session's own transcript logged, unprompted, after a failed send: "Relay failed again as expected (consistent pattern all session)" — so the failure is consistent enough that the session itself started expecting it.

Happy to share session IDs/timestamps if useful for correlating with server-side logs.

kcarriedo · 1 day ago

The "reports sent, never delivered" symptom after worktree recycle is a rough one - the sender has no way to know the message is being silently dropped, so multi-session coordination falls apart quietly rather than loudly.

I've run into a related pattern (not worktree-specific): in a coordinator + multiple peer session setup, messages sent to a session that's been idle for a while are accepted by the send_message call but the recipient never processes them. The session's event log shows the message arrived but no turn starts. The only reliable recovery is to attach to the recipient session interactively and prompt it manually, which defeats the purpose of autonomous multi-session coordination.

One thing that's helped slightly: before sending a coordinating message, having the sender first call list_events on the target session to confirm it's actually in a receiving state. If list_events shows recent activity, delivery is more reliable. If the session has been quiet for a while, the message tends to get dropped. Still not a real fix - you shouldn't need to pre-check liveness before every send_message - but it's surfaced a correlation worth logging.

The core issue feels like missing liveness semantics on the send_message surface: the caller needs to know whether delivery is "queued and guaranteed" vs "best-effort-and-may-drop", and right now it's the latter with a success status code.