SendMessage returns success but silently drops messages; Remote Control sessions should still do local inter-session messaging

Status Open
Reported on v2.1.247
Maintainer reply None cached
Activity 3 comments · opened Aug 31, 2026

Summary

Two related problems with cross-session messaging (SendMessage / ListAgents) when Remote Control is involved:

  1. Feature request: a session connected to Remote Control should still participate fully in local inter-session messaging.
  2. Bug (the damaging part): when messaging does break for a session, SendMessage still 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 bridgeSessionId set in ~/.claude/sessions/<pid>.json (Remote Control connected)

What we observed

One session (call it S1) stopped exchanging messages entirely:

  • Outbound: 8 consecutive SendMessage calls to 5 different peers all returned success with a msg_id. None of them appear in any recipient's transcript. Verified by grepping each recipient's ~/.claude/projects/<slug>/<sessionId>.jsonl for <cross-session-message user 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.
  • ListAgents continued 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 success plus a msg_id.
  • ListAgents still 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

  1. 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.
  2. Surface bridge/transport state in ListAgents. Today the annotation ; it is also connected via Remote Control appears on the recipient row, but there is no indication of the sender's own transport health. A row like myproj-a1 · interactive · messaging: degraded would have made this visible in seconds.
  3. 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.
  4. Optionally, a self-check — something like /remote-control reporting "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 SendMessage results against the recipient's .jsonl transcript.
  • It correlates with bridgeSessionId appearing 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.

View original on GitHub ↗

3 Comments

RT0305 · 1 month ago

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 sessionId are unchanged; only the display name moved. Since the name is the address SendMessage takes, a name captured from an earlier ListAgents can silently stop pointing at anything. Two sessions also disappeared from ListAgents and reappeared under new names, which reads from the caller's side as "those sessions died".

Suggestions: key SendMessage on sessionId (or expose it in ListAgents so 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:

  • S1 → 5 peers: 16+ sends, all success + msg_id, 0 arrivals
  • Another session sent successfully to two peers at 13:10 and those arrivals are present
  • S1's last genuine inbound: 11:07; last confirmed outbound delivery: 11:09. Both directions stopped within ~2 minutes of each other and have not recovered in 2 hours.
  • The only property distinguishing S1 from the five working sessions: it was started ~8.7 hours earlier (startedAt in 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. ListAgents kept 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.

// count genuine inbound cross-session messages in a peer's transcript
for (const line of fs.readFileSync(jsonl, 'utf8').split('\n')) {
  if (!line.trim()) continue;
  let o; try { o = JSON.parse(line) } catch { continue }
  if (o.type !== 'user') continue;
  const c = o.message?.content;
  if (typeof c !== 'string') continue;          // <- required
  if (!c.includes('<cross-session-message')) continue;
  console.log(o.timestamp, c.match(/from-name="([^"]+)"/)?.[1]);
}

Note also that the CCD app's list_sessions returns local_* identifiers from a different namespace with no corresponding .jsonl; cross-referencing those against the messaging registry makes sessions look like they vanished.

muddybulldog · 1 month ago

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). Two SendMessage calls to two different peers, both returned success: true with a msg_id, neither ever appeared in the recipient's transcript.

The bridgeSessionId correlation holds here, 2 for 2

From ~/.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.sendMessage in ~/Library/Logs/Claude/main.log, with a messageLength. 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

  • Not an undrained queue. The docs say messages drain at the recipient's next tool round. The recipient subsequently took several tool rounds (ToolSearch, ListAgents) across two separate prompts and reported "no messages have come in" each time.
  • Not a permission-class mismatch. The sender was in bypass mode and recipients in auto; setting all sessions to auto and re-sending changed nothing.
  • Not the transport. All three sockets in /tmp/cc-socks/ exist, all three PIDs are alive and holding them open (lsof), and all three accept connections. Same version, same peerProtocol: 1, same peerFeatures across 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: true and a msg_id, when neither had been. One was a production deploy request that sat undelivered while both sides believed it was in flight. notify_when_idle rides 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.

hipvlady · 1 month ago

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.