Cross-machine SendMessage works via undocumented bridge:<sessionId> address while every surface says reply-only/unreachable
Summary
Cold cross-machine SendMessage works when addressed by the raw bridge address (to: "bridge:<sessionId>"), but every user-facing surface — the SendMessage/ListAgents tool descriptions, the refusal error, and the cross-session messaging docs — states cross-machine sessions are reply-only and unreachable. The reply-only restriction is apparently enforced only in name resolution, not in the transport. Nothing documents the address form, so the working path is undiscoverable.
Either the address form is intended (then it should be documented and surfaced) or the name-layer policy is intended (then the transport shouldn't accept cold bridge sends). Currently the two layers disagree, and the docs describe the policy layer as if it were the wire.
Environment
- Claude Code 2.1.224 on both ends (macOS sender, Linux receiver — different physical machines)
- Same account, claude.ai OAuth (first-party API), Remote Control connected on both ends
- No relevant settings: no
isolatePeerMachines, nocrossSessionInbound, no flag-disabling env vars on either session
What the surfaces say
SendMessagerefusal (send addressed by roster name):
> 'NAME' matches a Remote Control session on this account, and those are reply-only from here: messageable only in reply, after it messages you first. The Claude Code Remote send_message connector is not a workaround either — it cannot reach these device-gated sessions […] it isn't reachable by name from this machine
ListAgentsdescription: remote bridge sessions "are reply-only — you can message one only in reply, after it messages you first, and no connector reaches it by name either."- Docs table (“Message sessions on other machines”): On another of your machines → Replies only.
SendMessagetoschema: documents names,main, and agentIds only — no address form of any kind.
Taken together these read as "cross-machine initiation is impossible." For two CLI sessions on different machines this is also a deadlock by construction: each sees the other as reply-only, so under the documented model no first message can ever exist.
What actually happens
- On the target machine, read the target session's bridge id from its session registry:
$CLAUDE_CONFIG_DIR/sessions/<pid>.json→bridgeSessionId(the same id appears in its/rcfooter URL,claude.ai/code/session_01…). - From the other machine, have Claude call
SendMessagewithto: "bridge:session_01…"— no prior inbound message, no reply address. - Result:
success: true, the message arrives at the remote session (verified on the receiving terminal), carrying the sender's ownbridge:id asfrom=— so the receiver can reply and the channel stays open both ways (verified: replies and further unprompted messages then flow in both directions).
Observed inbound handling on arrival: the cold bridge message asserted no permission class, so a bypass-permissions receiver held it for user approval (consistent with the documented inbound rules) — after one approval, subsequent traffic auto-delivered.
The address syntax isn't foreign to the product: every inbound cross-session message already exposes it in its from="bridge:…" attribute, and the documented reply flow is "copy from into to". The reply mechanism runs on an address scheme the schema never mentions accepting.
Why this is costly
The refusal message names two dead ends and preemptively debunks a third workaround, which makes it read as exhaustive. Two independent sessions on this account (one per machine) investigated the same question, both concluded "cross-machine SendMessage is non-functional / discovery-only," and one persisted that wrong conclusion to memory — hours of investigation for a fact that one line in the schema would have prevented. Discovering the working path required strings-level inspection of the CLI binary to find the address parser (uds: / bridge: schemes).
Suggestions
Whichever layer reflects intent, align the others with it:
- If bridge-address sends are intended: document
bridge:<sessionId>(anduds:) as validtovalues; include each row's address inListAgentsoutput (the roster fetch already has the ids and currently strips them); reword the refusal from "isn't reachable" to "isn't addressable by name — use its bridge address". - If reply-only is intended: enforce it in the transport, not only in name resolution, and the docs stand as written.
- Either way: the docs' "Replies only" table describes name-resolution policy as a transport property — worth clarifying which it is.
Related feature requests: #36181, #37213, #78706.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗