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)

Status Open
Reported on v2.1.222
Maintainer reply None cached
Activity 0 comments · opened Aug 20, 2026

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 returns
delivered: 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_message runs inside the desktop app and calls LocalSessions.sendMessage with

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
session …
line ever follows
, no entry is written to the recipient's transcript
(~/.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

  1. 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).

  1. From A, call mcp__ccd_session_mgmt__send_message targeting B with any text.
  2. Observe the receipt: delivered: true (or "Message sent to session …").
  3. Grep B's transcript dir: grep -r "<the text>" ~/.claude/projects/<B-slug>/ → absent.
  4. 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.

View original on GitHub ↗