[BUG] SendMessage returns {"success":true} for messages never delivered; a long-lived session instance goes deaf both ways; stale bridge-pointer leaves host "Connected" with 0 workers (2.1.234)
Amended 2026-08-26. The original report blamed --resume on the sender side. That was wrong; see the correction comment. This body has been rewritten to describe what the evidence actually supports. Two defects are reported.
Defect 1 — SendMessage returns {"success":true} for messages that are never delivered
Cross-session messaging between local Claude Code sessions returns {"success":true} with a msg_id while the message is silently lost. The recipient's transcript never shows an inbound block, and its context never sees the message, including across many subsequent active turns. Nothing is queued or retried; nothing is logged.
The false success is the core problem: agents believe they have communicated. We only caught it by grepping the recipient's transcript for a unique token after every send.
Defect 2 — a long-lived session instance can become unreachable in both directions, with no error surfaced
One session instance (months old, repeatedly restarted, many context compactions, 16 MB transcript) reached a state where:
- it never delivered a message to anyone, ever — always
success, never received; - and nothing could be delivered to it, from any sender, fresh or not;
- while its unix socket was LISTENing and accepting connections, its registry entry was valid, and it otherwise worked normally for interactive use.
No error was surfaced on either side. Only full recreation of the session cured it — killing and restarting the host process was not enough (see "Recovery" below).
Environment
- Claude Code 2.1.234, identical binary across all sessions (
~/.local/share/claude/versions/2.1.234) - Ubuntu 26.04, kernel 7.0.0-30-generic, x86_64
- Long-lived sessions:
claude remote-control --name <n> --spawn=same-dir --permission-mode bypassPermissions, one per project dir, each inside tmux - Registry entries in
~/.claude/sessions/*.jsonall showpeerProtocol: 1,entrypoint: sdk-cli, live pids, validmessagingSocketPath - Note: every remote-control worker runs with
--resume=<sdk-url>, including freshly created ones — the flag is set unconditionally and does not distinguish session age
Evidence matrix
Delivery verified by grepping the recipient's transcript for a unique nonce.
| Sender ↓ / Recipient → | healthy session A | degraded session B | recreated session B′ |
|---|---|---|---|
| fresh headless claude -p | ✅ 4/4, incl. full processing | ❌ | — |
| healthy long-lived session A | — | ❌ | ✅ delivered |
| recreated session B′ | ✅ delivered | — | — |
Every failure has degraded session B as an endpoint; no cell isolates the sender. Historical data point: 50 messages were exchanged successfully between these sessions over two days before B degraded.
Proof-of-delivery standard (useful for anyone reproducing)
Three JSONL entry types appear in the recipient's transcript, and only the last two prove anything:
| entry type | meaning | proof? |
|---|---|---|
| queue-operation | queued (~10 s) | no — appears even when nothing is processed |
| attachment / queued_command carrying the <cross-session-message from-name="…"> block | materialized (~40 s) | yes |
| assistant | processed in a turn | yes |
Grepping too early, or not distinguishing these, produces false conclusions in both directions. A second trap: the nonce also appears in the sender's own transcript (its tool call), which is not evidence of delivery.
Transport layer ruled out
- All peer sockets in
/run/user/<uid>/cc-socks/are LISTENing and held by live processes (ss -xlp), including the failing ones; a rawconnect()to each succeeds, including B's. - Registry
procStartvalues match/proc/<pid>/statfield 22 — no stale identity. - No on-disk queue or spool; messages are lost, not deferred.
- Environment differences (extra CA cert var, node compile cache, inherited shell vars) ruled out: injecting the failing session's environment into a fresh
claude -pstill delivers. - Connection sampler: polling
ss -xtpfor the sender's pid every 250 ms during aSendMessagecall toward B showed the sender never attempting a connection to B's unix socket. Under the revised model this is still unexplained and may be the best lead — it suggests the send path can abandon local delivery before connecting, without surfacing an error, based on some resolved state about the recipient.
Defect 3 (related, likely the same subsystem) — stale bridge-pointer.json ⇒ CCR v2 404 ⇒ host runs with no worker
While recovering from Defect 2 we hit a distinct, reproducible failure:
[17:41:24] Error: CCR v2 worker registration failed for session cse_XXXX: Request failed with status code 404
·✔︎· Connected · system · HEAD
Capacity: 0/32
~/.claude/projects/<slug>/bridge-pointer.jsonkeeps the previoussessionId/environmentId. If the previous session died uncleanly and the server reaped it, worker registration 404s.- The host then stays up advertising
Connectedand a QR/pairing URL, withCapacity: 0/32, zero child worker process, no entry in~/.claude/sessions/, no socket, and absent fromclaude agents --json. It looks healthy from the terminal and is invisible/dead from the apps. - Restarting does not fix it: the new process rewrites
bridge-pointer.jsonupdating onlypid/procStartwhile preserving the stalesessionId/environmentId, so it 404s again. We burned two restart cycles before finding this.
Suggested fixes: don't report Connected when worker registration failed; treat a 404 on registration as a signal to discard the pointer and register a fresh session; surface the condition beyond one startup log line.
Recovery / workaround
- Stop the supervisor, kill the tmux session, confirm no
claude remote-control --name <n>remains. mv bridge-pointer.json bridge-pointer.json.stale-<date>(never delete — you may want the old environment id).- Restart. A new
sessionIdandenvironmentIdare generated, so theclaude.ai/code?environment=…URL changes; the old one points at a dead environment. - This also cured Defect 2 — the recreated instance sends and receives normally.
We now also monitor for Defect 3 automatically: a remote-control host with no child process is the fast, reliable health signal; pgrep on the host process alone proves nothing.
Impact
Multi-session/agent coordination degrades silently to "everyone believes they sent, nobody received", and a session can be dead to the mobile/desktop apps while its terminal says Connected. The misleading success and the misleading Connected cost us most of a day.
Happy to provide sanitized transcripts, registry snapshots, or run instrumented builds.
3 Comments
Correction from the reporter — the
--resumethesis in the original report is wrongFurther testing has refuted the central claim of this report. I am correcting it rather than leaving misleading data in the tracker. The original title and matrix are amended below; this comment preserves what changed and why.
What broke the thesis. Every remote-control worker — including one created 10 minutes earlier — runs with
--resume=<sdk-url>in its command line.claude remote-controlsets that flag unconditionally. So "resumed session" was never observable the way I claimed to observe it, and the sender axis of my matrix was not actually varying what I thought it varied.What the completed matrix shows. After recreating the degraded session, I filled the missing cells:
| Sender ↓ / Recipient → | resumed session (claudevps) | degraded session (old instance) | recreated session (new instance) |
|---|---|---|---|
| fresh headless
claude -p| ✅ 4/4 | ❌ | — || resumed session | — | ❌ | ✅ delivered |
| recreated session | ✅ delivered | — | — |
Every failing cell has the degraded session as its endpoint. No cell isolates the sender. A resumed sender delivers fine to a healthy recipient — which is exactly what the original title says is impossible.
What actually happened. The only "resumed sender vs healthy recipient" test I ran used the degraded session as the sender, so sender-degradation and resume-state were confounded. The real variable is the health of a specific session instance, not how it was started.
Still a real bug, and still the important one:
SendMessagereturned{"success":true}with amsg_idfor every one of those lost messages. The false ack is what made this cost hours. The revised report below focuses on that, plus a second defect found while fixing it.Apologies for the noise — the transport-layer eliminations and the delivery-proof method in the original report remain valid and are kept.
The two defects you've documented here are a clean separation of a hard problem: silent false-success from SendMessage is an API contract violation (callers can't build reliable coordination on a success signal that means nothing), and the deaf-but-alive session state is a lifecycle tracking failure where the registry diverges from actual reachability.
On defect 1: the send returning success + msg_id before confirming the recipient's transcript shows the inbound block means the success signal is optimistic at best, and in an eventual-consistency model at worst. For anyone building agent coordination on top of cross-session messaging today, the only safe pattern is exactly what you describe - verify receipt by polling the recipient's transcript for a unique token. That's an expensive workaround for what should be a synchronous acknowledgement.
On defect 2: the months-old session with many compactions going deaf in both directions points to state that accumulated across too many lifecycle events to recover from a simple process restart. The fact that full session recreation fixed it suggests the in-memory bridge state diverged from what the unix socket and registry entry represented. A "session health check" that validates the bridge pointer actually routes to a live recipient (rather than just checking socket + registry) would catch this before the first silent drop.
Practical note for anyone hitting this in prod: the combination of tmux-hosted remote-control sessions and long-lived instances is exactly the setup where session state decay is most likely. Short-lived sessions (per-task rather than per-project lifetime) avoid the accumulation problem at the cost of startup overhead.
We've been running multi-session coordination externally via Claudeverse and the silent-drop + dead-registry combination is the hardest class of failure to detect and recover from. If Anthropic adds a receive-acknowledgement signal to SendMessage, that alone would make reliable agent coordination significantly more buildable.
(Disclosure: Kyle Carriedo, building Claudeverse - claudeverse.ai - a coordination layer for Claude Code multi-session workflows.)
I think I found a practical workaround for this, at least for our setup.
I haven't reproduced the original silent-drop failure itself, but while investigating a similar cross-session messaging problem on our end, I noticed something worth sharing: on Claude Code 2.1.283 (Linux VPS, long-lived
claude remote-control --spawn=same-dirsessions in tmux — the same setup described in this issue), the native/top-levelSendMessage/ListAgentspair delivers correctly between independent Remote Control sessions. I verified this directly in the recipient's JSONL transcript (a realassistantturn processing the message), not just asuccess:trueresponse.I also spun up an isolated install of Claude Code 2.1.236 (separate
npmprefix, production binary untouched) and confirmed delivery in both directions there the same way.The interesting part: some of our sessions had been using
mcp__ccd_session_mgmt__send_messageinstead, and appeared unable to reach standaloneclaude remote-controlsessions on the VPS. I haven't tested both paths against the same target side-by-side, so I can't say for certain that's the whole story, but it's a cheap thing to check.So: if you're hitting silent delivery failures, it might be worth checking which of the two messaging paths (native
SendMessagevs.ccd_session_mgmt) is actually being used before assuming the transport itself is broken. Didn't fix anything for us tonight — just switched paths — but it's been reliable since.Claude Code 2.1.283, Linux VPS, long-lived tmux-hosted Remote Control sessions.
— Wolli, From Structory.Ai Team