acpmux: a sub-session answers to its short name; a miss names the close matches

Merged
TE
6d ago
+830-58

Leo, 2026-10-30: "i feel like there's still a big mess of uncategorized stuff in the sidebar + various not as ergonomic/one-command-y as i'd liike set of commands for interacting with stuff" / "good intuitive stuff" / "and also docs!"

Part of HQ#5197.

Carried on top of #28000

This lane took this PR over: its first lane is gone, and it now builds on #28000 (one session-key rule in the daemon, hub/key_route.rs). The original commits keep their authorship. On top of them:

  • The short name is key_route's last tier. A bare key that no whole name or id prefix matches anywhere, and that no name here starts with, names the one sub-session on any host whose last name segment it is (lane-3 for coord/lane-3). Two such sub-sessions are refused with the same session_ambiguous refusal #28000 gives, which prints the exact HOST/NAME command for each. A key with a / is a whole name only, never a short name.
  • Hub::resolve, resolve_remote, locate::by_key and the close-matches refusal are as below. They now serve the callers that key_route passes over (web and peer connections, and keys that are exact already).
  • Test fixtures use neutral sample host names (fleet rule lint).

Original description

What was wrong

acpmux send lane-3 failed with no_session when the lane is really the coordinator's sub-session coord/lane-3 (a child is named PARENT/SHORT). acpmux ls shows the short name under the parent, but no resolver took it. The refusal then said only "run acpmux ls", which on a busy host means reading thousands of rows.

What changes

  • One rule, in every resolver. session_name::ends_with_segments(name, key): key is the name's last /-separated segments (lane-3 and coord/lane-3 name coord/lane-3; ci-3 does not). It is applied after an exact id or whole name misses and ahead of a prefix in:
    • Hub::resolve (local sessions): what send, last, wait, info, watch and the other name-taking commands reach through _acpmux/info / the send queue;
    • Hub::resolve_remote / remote_matches (a peer's sessions, also PEER/SHORT);
    • _acpmux/resolve (hub/locate.rs), used by session resume|stop|cancel and retry.
  • Precedence stays safe. A whole name always wins over a short name, including a peer's session with exactly that name (the local short-name match then gives way, as before). Two sessions with the same short name are refused with their names and ids, even if a third session has the key as a prefix.
  • A miss names the close matches. no session matches "lane"; close matches: coord/lane-3, hb/lane-30; use one of those names, or run acpmux ls. Also in --json as error.data.candidates (at most 8). The refusal is still no_session, exit 4, and it still starts with no session matches, which cmux-tui-core and the webview send parser read. Candidates are only given to local-socket callers. A peer connection gets the plain refusal it got before.
  • Hot path: Hub::resolve still reads each name once under its meta lock, in one pass now. The close-match scan runs only where a refusal goes back to a caller (Hub::resolve_told, the send queue's refusal, _acpmux/resolve's not-found), never when a request is just probed to see if it belongs to a peer.
  • README: the name forms now include the short name.

Tests

  • session_name::tests: segment boundary rules, candidate ranking/dedup, refusal text and data.
  • hub::own_host::segment_tests: short name found locally; whole name beats short name here and on a peer; ties refused with names; peer short names (lane-3, PEER/lane-3); resolve_told lists local and peer close matches, and a peer connection gets none.
  • hub::locate::tests::a_sub_session_is_found_by_its_short_name: the control resolver, including HOST/SHORT.
  • cli::errors::typed_error_tests::a_no_session_refusal_names_the_close_matches: candidates reach message and envelope; still no_session exit 4, so the send relay (sendRelay) still takes it.

Agent session: agent session

šŸ¤– Generated with Claude Code