[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)

Status Open
Reported on v2.1.234
Maintainer reply None cached
Activity 3 comments · opened Aug 26, 2026
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/*.json all show peerProtocol: 1, entrypoint: sdk-cli, live pids, valid messagingSocketPath
  • 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 raw connect() to each succeeds, including B's.
  • Registry procStart values match /proc/<pid>/stat field 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 -p still delivers.
  • Connection sampler: polling ss -xtp for the sender's pid every 250 ms during a SendMessage call 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.json keeps the previous sessionId/environmentId. If the previous session died uncleanly and the server reaped it, worker registration 404s.
  • The host then stays up advertising Connected and a QR/pairing URL, with Capacity: 0/32, zero child worker process, no entry in ~/.claude/sessions/, no socket, and absent from claude 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.json updating only pid/procStart while preserving the stale sessionId/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

  1. Stop the supervisor, kill the tmux session, confirm no claude remote-control --name <n> remains.
  2. mv bridge-pointer.json bridge-pointer.json.stale-<date> (never delete — you may want the old environment id).
  3. Restart. A new sessionId and environmentId are generated, so the claude.ai/code?environment=… URL changes; the old one points at a dead environment.
  4. 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.

View original on GitHub ↗

3 Comments

MyrkoF · 1 month ago

Correction from the reporter — the --resume thesis in the original report is wrong

Further 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-control sets 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: SendMessage returned {"success":true} with a msg_id for 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.

kcarriedo · 1 month ago

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

Larose75-precogn · 17 days ago

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-dir sessions in tmux — the same setup described in this issue), the native/top-level SendMessage / ListAgents pair delivers correctly between independent Remote Control sessions. I verified this directly in the recipient's JSONL transcript (a real assistant turn processing the message), not just a success:true response.

I also spun up an isolated install of Claude Code 2.1.236 (separate npm prefix, 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_message instead, and appeared unable to reach standalone claude remote-control sessions 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 SendMessage vs. 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