Repository navigation
acpmux: an ssh peer that connected before the remote wrote its peer token no longer stays served as Web - #24105
Conversation
…r once the remote has one (red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ca9-5ac5-7692-adfc-a3ae828256ca&host=huge-blue
…t again and reconnects as a peer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ca9-5ac5-7692-adfc-a3ae828256ca&host=huge-blue
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Owned by acpmux-unify-3 on huge-blue · session |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on de67de9 to compare; do not merge until green; see lane-rules |
|
Found 8 test failures on Blacksmith runners: Failures
|
|
Bead |
…and reconnect when it changed under a Web-served link Review (Codex xhigh) on 6e48a42: a relink after the handshake could lose or replay queued RPCs and skipped the disconnect notice; a token rotated after a non-empty read was never retried. Now the rereads happen before the connect, and the stale-token check runs right after initialize, before any watch or attach, and drops the link as an error (normal disconnect path). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ca9-5ac5-7692-adfc-a3ae828256ca&host=huge-blue
… queued meanwhile Codex re-review on 0c0b6e1: requests queued while the probe ran could go out on the Web link or replay on the next connection after their callers got the disconnect error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ca9-5ac5-7692-adfc-a3ae828256ca&host=huge-blue
…l the stale-token check clears it Codex re-review on affe6c9: draining the queue was no admission barrier (a request could go out on the Web link while the probe ran, or be enqueued after the drain), and it dropped accepted notifications such as a remote session/cancel. Callers now get the unreachable error until the link is admitted; the handshake's own initialize bypasses the check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ca9-5ac5-7692-adfc-a3ae828256ca&host=huge-blue
…queued for the next connection Codex re-review on 7518ffd: a remote session/cancel sent while the peer was down returned success and replayed on reconnect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ca9-5ac5-7692-adfc-a3ae828256ca&host=huge-blue
…-peer-send-mac-perms # Conflicts: # cmux-tui/crates/acpmux/src/peer.rs
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
…started remote writes its new one stale_token() read the remote once after a Web handshake; a remote that had removed the old token and not yet written its new one read as None, so the link stayed Web. While the presented token is gone, reread it (TOKEN_REREADS) before admission clears. Test: A -> missing -> B. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a175c0-3004-7a40-8069-4149d94915ab&host=huge-blue
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Review (codex gpt-6-astra xhigh, correctness first): SHIP at c16e334 Diff-only adversarial review on cmux16. First pass at 5ecc7aa flagged that stale-token recovery read the remote once, so a token that vanished before the new one was written left the link served as Web. c16e334 rereads with the existing backoff and adds a test for token A, then none, then B (fails on the old code). Second pass: no blocking findings; a test-coverage note on requests during admission is not blocking. |
…-peer-send-mac-perms # Conflicts: # cmux-tui/crates/acpmux/src/peer.rs
…d drops frames queued for a dead link A connection that presented no token rereads the remote (TOKEN_REREADS) once admitted and reconnects as a peer when the token appears. Frames still queued when a link opens were enqueued for the one that dropped (whose callers already failed), so they are discarded, never replayed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a175c0-3004-7a40-8069-4149d94915ab&host=huge-blue
…e instead of waiting for good request() checked connected, then registered its pending entry; if run() failed what waited in between and the next link discarded the queued frame, a prompt (no deadline) waited forever. The request now notes the link epoch before its check and fails when the epoch moved by the time it is registered. A notification on such a link is dropped as before (fire-and-forget). The late-token rereads keep serving as Web meanwhile, as the link did before for good; documented at the call. Regression test a_prompt_admitted_on_a_link_that_went_down_fails_instead_of_waiting fails without the fix (5 s timeout) and passes with it; peer_token_race 5/5, peer_long_turn 3/3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a175c0-3004-7a40-8069-4149d94915ab&host=huge-blue
…re admission, and stop interrupts them - Every outbound frame is tagged with the link epoch it was admitted on; serve drops one whose link ended when it dequeues it (replaces the one-shot drain at link start, which a blocked sender could slip past). - The no-token rereads move into stale_token, before admitting clears: a live link is never torn down for a late token (late_token removed). - Reread sleeps select on stop_signal and stop is checked before each ssh read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-c920712e011b0d6a1288
|
Review (codex gpt-6-astra xhigh, correctness first): SHIP at 1629e90 Diff-only adversarial review after two base merges (#24268 peer prompt no-cap). Earlier passes found and this head fixes: a token that vanished before the new one was written, a token written after the handshake, outbound frames admitted on a dead link going out on the next one (now epoch-tagged and dropped on dequeue), stop() not interrupting rereads, and a prompt admitted on a dead link waiting forever. Each has a regression test (peer_token_race 5/5, peer_long_turn 3/3, link_epoch unit tests). Final pass: no blocking defects; pre-existing (unchanged from base): a configured peer_token is never reread. |
|
Shipped in:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
deputy-2 (cosmic-gray), verbatim: "from cosmic-gray,
acpmux send air-blue/gate-rules ...is refused with "This chat runs with your Mac's Claude permissions. Start a new chat from this device to control it remotely." The session was imported onto air-blue (a Mac) by acpmux session import and runs bypassPermissions; sending through air-blue's own daemon works. Coordinators on cosmic-gray have to steer lanes on other hosts, so a peer that the host lists inacpmux host(same user, ssh-authenticated) should be allowed to send, or the import should record the session as remotely controllable."Cause. The policy is already what deputy-2 asks for. A proven peer daemon (
Control::Peer) is exempt from the Mac-permissions refusal (web_control_verdictreturns early for anything butControl::Web). The link from cosmic-gray to air-blue wasn't proven:acpmux host lson cosmic-gray showedair-blue … servedAs: remotewhile cmux16 and huge-blue showedpeer. air-blue's daemon had just restarted for the #23880 rollout, and cosmic-gray reconnected inside the window where the new daemon is listening but hasn't yet writtenrun/peer.token; the old daemon had already removed its own. In the air-blue log, the listener came up at 02:22:05 and the token was written at 02:22:08, and cosmic-gray connected at 02:22:10 with an older timestamp still in flight. The ssh token read came back empty, so the connection was served as Web. It stays Web, holding every send to the Web's rules, until the link next drops.Fix (narrow, no policy change). Two parts, both on the
ssh://path only:run/peer.tokenyet.initialize, before any watch or attach: if the remote served this link as Web (remote), the peer reads the token once more. If the remote now has a token this connection didn't present (it restarted between the read and the handshake), the link is dropped as an ordinary error. The normal disconnect path then runs (pending requests fail,Disconnectednotice, backoff), and the next connect presents the new token. Because nothing has been watched, attached or sent on the link yet, nothing is lost or replayed.A remote that is too old to make a token, or one that serves this daemon as Web for another reason (it has the same token we presented), stays Web, as before. That costs at most five ssh reads per connection and never loops.
peer_auth::is_peerstill needs the launch's token and a loopback transport, and the Web refusal (#23792 / D13) is untouched.I rejected deputy-2's option (b), recording imported sessions as remotely controllable. It would widen the Web rule for every Web client (paired devices and the dashboard), not just peers.
Remote relay analysis (skills/cmux-socket-policy/references/remote-relay-authorization.md)
read_config_argvssh command the peer already runs at every connect (fixed argv, the destination parsed byssh_target, which refuses anything option-shaped). There are at most four extra reads per connection: three while the remote has no token, and one if the remote served the link as Web.carries_peer_tokenis unchanged; this path is ssh-only).tests/peer_token_race.rsdrives a real remote daemon through a stand-insshwhose first token read finds none. The existingpeer_origin.rs(token plus dashboard token makes Peer; the dashboard token alone stays Web; no RPC returns the token),peer_transport.rs(the token never crosses plain ws:// to a non-loopback host),ssh_peer_injection.rsandremote_guard_modes.rs(a Web connection is still refused on a Mac Claude session) all pass on the head.Testing
Focused command, run on huge-blue:
cargo test -p acpmux --test peer_token_race. It has three cases, each a real remote daemon behind a stand-inssh: the remote has no token on the first read (late), the remote shows the previous run's token on the first read (stale), and the remote never has a token (never).peer.rsat merge-base 986cfe3, with this test file: FAILED, 3 of 3.lateandstale:the peer is not served as peer: {… "servedAs":"remote" …}after 60 s.never:reads of a remote with no token left: 1 right: 5.Also run on the head:
cargo test -p acpmux --test peer_origin --test peer_transport --test ssh_peer_injection --test remote_guard_modes --test peer_degraded --test peer_lease --test peer_canonical_endpoint(all ok),cargo test -p acpmux --lib peer(57 passed),cargo clippy -p acpmux --all-targetsandcargo fmt --check(clean).Review: Codex at xhigh reviewed 6e48a42 and returned FIX. Its points were that a relink after the handshake could lose or replay queued RPCs and skipped the disconnect notice, and that a token rotated after a non-empty read was never retried. Both are addressed in 0c0b6e1 (above). Re-review is on the final head.
Observed / Not run
servedAs: remotefor air-blue on cosmic-gray's livehost ls.Changelog
Fixed: a cmux machine whose peer restarted no longer stays held to remote-device rules, which refused sends to chats running with the Mac's own Claude permissions.
risk:high (peer authentication path).
Agent session: huge-blue
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.