SendMessage returns success but silently drops messages; Remote Control sessions should still do local inter-session messaging
Summary
Two related problems with cross-session messaging (SendMessage / ListAgents) when Remote Control is involved:
- Feature request: a session connected to Remote Control should still participate fully in local inter-session messaging.
- Bug (the damaging part): when messaging does break for a session,
SendMessagestill returns success. Nothing surfaces the failure, so both sides sit waiting on each other.
The silent-success part is what actually cost us time — roughly two hours of two agent sessions each believing the other had gone quiet.
Environment
- Claude Code 2.1.247,
entrypoint: claude-desktop, Windows 11 - 5–6 concurrent interactive sessions in the same working directory
- Two of them had
bridgeSessionIdset in~/.claude/sessions/<pid>.json(Remote Control connected)
What we observed
One session (call it S1) stopped exchanging messages entirely:
- Outbound: 8 consecutive
SendMessagecalls to 5 different peers all returnedsuccesswith amsg_id. None of them appear in any recipient's transcript. Verified by grepping each recipient's~/.claude/projects/<slug>/<sessionId>.jsonlfor<cross-session-messageuser turns. - Inbound: S1's last genuine inbound message was at 11:07 local time. Nothing after.
- Both directions stopped within ~3 minutes of each other, and within ~3 minutes of S1's registry entry being rewritten with a
bridgeSessionId. ListAgentscontinued to list peers normally the whole time, so nothing in the UI suggested a problem.
Meanwhile a second session that was also bridged (bridgeSessionId present) kept exchanging messages with an unbridged peer normally, in both directions, during the same window. So "bridged = broken" is not the rule — whatever the trigger is, it is narrower than that. We could not isolate it further from the client side.
Why this is hard to notice
There is no observable difference between "delivered" and "silently dropped":
- The tool result is
successplus amsg_id. ListAgentsstill shows the peer as alive and interactive.- The only way we found to check delivery was to read the recipient's transcript file directly and grep for the message.
Two sessions each concluded the other was unresponsive, and each wrote that into its own status reports. A blocked production promotion sat idle for ~50 minutes because an approval message was never delivered.
Suggested fixes, in priority order
- Do not report success for a message that was not delivered. Either await an ack from the recipient socket, or return a distinct result (
queued,undeliverable,unknown) so the caller can retry or escalate. - Surface bridge/transport state in
ListAgents. Today the annotation; it is also connected via Remote Controlappears on the recipient row, but there is no indication of the sender's own transport health. A row likemyproj-a1 · interactive · messaging: degradedwould have made this visible in seconds. - Let Remote Control sessions participate in local messaging (the original request). A session being reachable from claude.ai/code or mobile should not change its local peer connectivity.
- Optionally, a self-check — something like
/remote-controlreporting "local messaging: OK / not delivering" — would make this diagnosable without reading raw transcript files.
Reproduction
We could not reduce this to a deterministic repro; it appeared during a normal multi-session working day and persisted for hours. What we can say precisely:
- It is observable by comparing
SendMessageresults against the recipient's.jsonltranscript. - It correlates with
bridgeSessionIdappearing in~/.claude/sessions/<pid>.json, but that correlation has a counterexample on the same machine at the same time (n=1 for the failing case), so we are reporting the correlation rather than asserting causation.
Happy to provide more detail if useful.
3 Comments
Follow-up with two measurements that narrow this down. Remote Control turned out not to be the cause — the user disconnected it on every session and the failure persisted unchanged, so please disregard the bridge correlation in the original report.
1. Session display names rotate while the session itself persists
Same machine, same working directory, ~32 minutes apart, reading
~/.claude/sessions/<pid>.json:| time |
name|sessionId|pid||---|---|---|---|
| 12:42 |
myproj-1e|e99d01dd-…| 29596 || 13:14 |
myproj-6a|e99d01dd-…(same) | 29596 (same) || 12:42 |
myproj-cd|e2331b23-…| 34604 || 13:14 |
myproj-9f|e2331b23-…(same) | 34604 (same) |The session, pid and
sessionIdare unchanged; only the display name moved. Since the name is the addressSendMessagetakes, a name captured from an earlierListAgentscan silently stop pointing at anything. Two sessions also disappeared fromListAgentsand reappeared under new names, which reads from the caller's side as "those sessions died".Suggestions: key
SendMessageonsessionId(or expose it inListAgentsso callers can pin to something stable), and if a name must rotate, fail loudly when a send targets a name that no longer resolves.2. The failure is per-sender, and the sender is the oldest session
At 13:14, of six local sessions, exactly one (S1) cannot deliver. Verified by reading every recipient's
.jsonl:success+msg_id, 0 arrivalsstartedAtin the registry). It has been through several context compactions.That suggests a long-lived session's messaging socket or peer registration can go stale — and, critically, that nothing in the client notices.
ListAgentskept listing peers normally throughout.Detection script
For anyone hitting this, delivery can only be confirmed from the recipient's transcript. One trap worth naming: filter on
typeof content === "string". Without it, a quoted transcript inside your own tool output (e.g. reading another session's history) is counted as an inbound message — that produced a false "delivery confirmed" for us before we caught it.Note also that the CCD app's
list_sessionsreturnslocal_*identifiers from a different namespace with no corresponding.jsonl; cross-referencing those against the messaging registry makes sessions look like they vanished.Independent reproduction on macOS — same version, and some evidence that narrows where the drop happens
Hitting this on
2.1.247,entrypoint: claude-desktop, macOS 27.0 (the original report is Windows 11, so it's not platform-specific). TwoSendMessagecalls to two different peers, both returnedsuccess: truewith amsg_id, neither ever appeared in the recipient's transcript.The
bridgeSessionIdcorrelation holds here, 2 for 2From
~/.claude/sessions/<pid>.json:| session | role |
bridgeSessionId||---|---|---|
| A | sender | absent |
| B | recipient — dropped | present |
| C | recipient — dropped | present |
Every recipient I messaged was Remote Control–bridged and every message was dropped; my sender was not bridged. I have no counter-example either way, so this doesn't contradict the report of a bridged session that kept working — but it's consistent with the recipient's bridge state being the axis rather than the sender's.
The drop is on delivery, not on send
The desktop app logs every injection into a session's conversation as
LocalSessions.sendMessagein~/Library/Logs/Claude/main.log, with amessageLength. On the day in question the operator's own typed messages are all present (13 chars, 27 chars, etc.). My two peer messages were ~2,000 and ~2,400 characters — no log line of any comparable length exists; the only multi-KB entries are from three days earlier. So the message never reaches the path that actually puts content into a bridged, desktop-hosted conversation.Ruled out, with tests
ToolSearch,ListAgents) across two separate prompts and reported "no messages have come in" each time./tmp/cc-socks/exist, all three PIDs are alive and holding them open (lsof), and all three accept connections. Sameversion, samepeerProtocol: 1, samepeerFeaturesacross every session.Untested prediction, if it's useful to whoever picks this up: if the recipient's bridge state is the discriminator, then sending to a non-bridged session should succeed. That's a one-line check for anyone who can reproduce.
On the suggested fixes — strongly agree that #1 is the one that matters. Real cost here: I reported two handoffs to the operator as delivered, on the strength of
success: trueand amsg_id, when neither had been. One was a production deploy request that sat undelivered while both sides believed it was in flight.notify_when_idlerides the same channel and is equally silent, so the "subscribe and wait" pattern fails the same way. Any distinct status —queued,undeliverable,unknown— would have made this visible in seconds instead of days.Worth flagging that these two findings aren't actually in tension, even though they look like they are on a fast read.
RT0305 ruled out Remote Control by testing the sender side, disconnecting it on every session and watching the failure persist unchanged. muddybulldog's 2-for-2 correlation is about the recipient's bridge state, a variable the original report never tested. Both can be true at once. A sender's own bridge connection being irrelevant says nothing about whether delivery depends on what the recipient is connected to.
That makes muddybulldog's untested prediction the sharper next experiment than it might look. Send from an unbridged sender to a bridged recipient and an unbridged recipient in the same batch. If only the bridged one drops, that isolates the variable cleanly, independent of RT0305's session-age or name-rotation findings.
The desktop log evidence is the most specific data point either report has produced. No entry at all for the dropped messages, while same-day typed messages of a few dozen characters show up normally in the same log. That points at something before local injection into the bridged conversation, not a queue that's slow to drain or a delivery that arrives and gets miscounted.