acpmux: session/auth asks the peer and lists sessionAuth

Merged
TE
8d ago
+293-40

Leo: “you gotta re-authenticate. You gotta log in again on the modal picker before the user even sends a prompt.”

This is the fourth of four PRs for _acpmux/session/auth, and it finishes the contract: it asks peers and lists the sessionAuth feature. It builds on #26700 (route probe and target), which has merged. Together with #26700, this was one PR. That PR got a SHIP from the reviewer and was split to stay under 800 lines; the reviewer then caught one more gap in this half (a target's routeId lost in the fallback replies), fixed in the last commit.

Peer link (evidence 1). A peer's chat, or a target whose host names a peer, is forwarded to that peer. The answer comes back with host set, and login.host set for a terminal login.

  • If the link is down, or the request isn't sent, or the link drops under it, this daemon answers peerUnreachable. The source is peerLink and evidenceAt is when the link went down. "Isn't sent" and "drops under it" are peer_unreachable and peer_disconnected from #26756.
  • If the link is up but the peer doesn't answer in time, the answer is unknown.
  • If the peer runs an acpmux that doesn't list sessionAuth, the answer is unknown, with a detail naming its build and acpmux host update NAME.
  • Any other error is the peer's own answer (a chat it doesn't have, bad params) and is returned as is.
  • The forward carries the Web origin mark (remote_guard::mark_forwarded), so a browser page can't run a peer's probe or check through this daemon.
  • An unknown peer name is invalid params.
  • A target's own routeId stays in every reply this daemon gives for a peer that can't answer; a chat's route is the peer's to know, so it stays null.

Listing.

  • sessionAuth is in initialize's features.
  • The method is in extensions.
  • peer.rs reads the peer's sessionAuth at handshake (supports_session_auth).
  • capabilities has session-auth-v1.
  • docs/acpmux-schema.json has the method.
  • peer_forward still excludes the method, so its own handler forwards it.

Tests. After is this head, on cmux-ci run 40694009841 at the rebased head (presence and subagent_retry in hub_integration are flaky on base; codex_auth_broker fails the same way on base, run 40665877991; the earlier head dfb39906 passed every suite on run 40669592664): fmt and clippy -D warnings are clean; acpmux lib, session_auth, hub_integration, the route suites, typed_errors, harness_health, claude_auth_env and no_keychain pass. The before runs, where each failed on the tree without its fix, are base 18af98cc with only the tests added (run 40634054284) and run 40664015700:

  • session_auth_asks_the_peer_and_answers_for_a_peer_that_is_down, with fake peer daemons:
    • A peer with the feature answers (signedOut, with host and login.host set).
    • A peer whose link went down gives peerUnreachable.
    • A peer that drops the link on the request gives peerUnreachable.
    • A peer never reached, for a target, gives peerUnreachable with evidenceAt.
    • A peer without the feature gives unknown, naming its build.
    • A routed target on a peer that can't answer keeps its routeId; this failed before the fix (run 40675958828).
    • Before: FAILED (run 40634054284). After: ok.
  • session_auth_is_listed_in_initialize: before FAILED (run 40634054284), after ok.
  • server::session_auth::tests::a_link_failure_is_read_from_the_links_own_words: the link's "timed out after Ns", "connection closed" and "dropped the request", and never another peer's words. Before FAILED (run 40664015700), after ok.

Risk: low. It is read-only. A request goes to a peer only when its chat or target names that peer, and a peer that can't answer gets an answer from the link instead.

No capture: no UI changes; this is a daemon method no view calls yet.

Agent session: agent session

🤖 Generated with Claude Code