Cross-session send_message: delivered:true returned for cold targets, then the message is silently dropped (regression, CLI 2.1.222 → 2.1.227/2.1.229)
Summary
In Claude Desktop's cross-session messaging (mcp__ccd_session_mgmt__send_message), a
peer message sent to a session whose CLI process is not currently live returnsdelivered: true — the app then cold-resumes the recipient session, but the resumed
CLI never converts the pending peer message into a turn. The message exists only in
app memory and is lost. There is no dead-letter queue, no retry, and no error. The
receipt is a lie for every cold target.
Messages to sessions with a live input stream deliver fine, which makes the failure
look random from the user's side: the same workflow works when a tab happens to be
active and silently loses messages the rest of the time.
Environment
- macOS 26 (Darwin 25.3.0), Apple Silicon
- Claude Desktop app (auto-updated; bug present on the app version current 2026-08-20,
UI shows v1.32885.1)
- Claude Code CLI: regression window 2.1.222 → 2.1.227/2.1.229. Verified working on
2.1.222 (cold-resume deliveries succeeded through 2026-08-10). First proven failure
2026-08-15 on 2.1.227. Still broken on 2.1.229. The desktop app also updated in the
same window, so app vs CLI cannot be split from disk evidence — but the receipt
behavior below is in the app bundle.
What we found in the app bundle (app.asar)
send_messageruns inside the desktop app and callsLocalSessions.sendMessagewith
origin: {kind: 'peer'}, wrapping the text as <cross-session-message from="local_…">.
- The cold-start path ends in
return o.isStopping ? {delivered:!1,…} : {delivered:!0}—
i.e. **delivered:true is returned when the resume spawn is initiated, not when the
message becomes a turn**.
Failure anatomy (from main.log + transcripts, 2026-08-18 07:52Z broadcast)
One session broadcast to 7 idle targets; all receipts said sent/delivered. For each
target the log shows the cold resume start:
Loaded 3325 transcript messages for session local_ff5b8dc6…
…but then, unlike a healthy delivery, no Mapping internal session local_… to CLI line ever follows, no entry is written to the recipient's transcript
session …
(~/.claude/projects/<slug>/*.jsonl), and the next health line reads:
[CCD CycleHealth] healthy cycle for local_e8049276… (638s, hadFirstResponse=false)
Compare a healthy pre-regression delivery (2026-08-09, CLI 2.1.222): identical resume,
followed by the Mapping line and a transcript entry within 59s.
Control experiment: 11 minutes after the same session swallowed the peer broadcast,
a plain user-nudge (LocalSessions.sendMessage … priority=next) to that exact session
landed instantly. Only peer-origin cold deliveries vanish.
Also observed: a peer message to an idle-but-open tab was held and materialized
exactly when the user focused that tab (setFocusedSession → message flushed into
the now-live stream, 20 minutes after its receipt). So held messages flush only if the
target becomes live before app restart; otherwise they are gone.
Repro steps
- Open two Claude Desktop sessions A and B (different projects). Let B go fully idle
(no in-flight turn; leave it long enough that no CLI process is live for it, or
quit/relaunch the app and don't focus B).
- From A, call
mcp__ccd_session_mgmt__send_messagetargeting B with any text. - Observe the receipt:
delivered: true(or "Message sent to session …"). - Grep B's transcript dir:
grep -r "<the text>" ~/.claude/projects/<B-slug>/→ absent. - Open B's tab and interact: the message never appears; it is not in any transcript,
not in ~/.claude/daemon/dispatch/ (empty), and no session-state file holds a
message buffer.
Expected
Either (a) delivered:true only after the message is durably enqueued where the
resumed CLI will actually consume it, or (b) an honest delivered:false /
queued-with-persistence, or (c) a persisted queue that survives until the target
processes it. Any of these; the current combination — positive receipt + in-memory-only
hold + a resume path that never consumes it — is silent data loss.
Impact
Multi-session workflows (a coordinator session dispatching work to project sessions)
break invisibly: senders trust receipts, recipients never know anything was sent, and
users burn days re-sending and hand-carrying messages between tabs. We ran a full
forensic reconstruction to establish the above; happy to provide redacted log excerpts.
Workaround we adopted (works)
- Message-as-file: writes to a shared inbox file are the payload (cannot be lost).
- The CLI-level UDS SendMessage channel (
/tmp/cc-socks/<pid>.sock) as the doorbell:
it delivers to live CLI processes and fails loudly for dead ones — a trustworthy
receipt either way.
- SessionStart hooks print unread inbox items so cold sessions brief themselves on open.