acpmux: a peer from before #28182 whose list is too large still links (pages of rows)

TE
6d ago
+166-7

A peer link's handshake reads the peer's session list with _acpmux/watch. A peer from before #28182 answers a list with no limit whole or not at all. On 2026-10-30 one fleet Mac's daemon (the NIGHTLY app's bundled build, c230bb58a) sent a 16.4 MB list, over the 15 MiB message limit even without tools. The handshake asked with tools, then without, got message.too_large both times and never connected. So every session on that Mac read as unknown from the other machines, and the fleet's PR-owner lookup read the owners of PRs there as UNKNOWN.

#28182's byte pages only help a peer that has #28182. This fixes it on the client side, so a peer that can't update right away still links.

What changed

peer.rs handshake: when the watch list is too large without tools too, it is read in pages of a count of rows. Those are the keyset pages every peer serves, pre-#28182 ones included: _acpmux/watch {enabled, withTools: false, limit}, then _acpmux/sessions {withTools: false, limit, cursor}.

  • Page size: 512 rows. That's about 1 MB at the incident's row sizes without tools. It's also few enough pages that a pre-#28182 peer's list pace (each page costs its share of a list, at least a sixteenth) adds about one list's wait.
  • A page refused as too large is asked for again with half the rows.
  • A page the peer paces (list.paced) is asked for again after the retryAfterMs it names, capped at 30 s. The handshake's own deadline still bounds the whole read.
  • A cursor given twice, or one that is not a string, ends the handshake, as for byte pages.
  • The reader gathers these pages the way it gathers #28182's byte pages (watch_key): it publishes the list once, whole, after the last page, and holds session_changed notices until then.

The order is unchanged until both whole requests fail: the list with tools, then without, then these pages. A peer on #28182 or later never gets here, since its watch replies in byte pages instead of refusing. As with the earlier no-tools step, rows read this way carry no tools.

No message limit was raised.

Test

cargo test -p acpmux --lib -- handshake_tests, run on this lane VM:

  • a_watch_list_too_large_from_an_old_peer_is_read_in_pages_of_rows: a fake pre-#28182 peer with 500 rows of 40 KB (20 MB, over the limit). It sends a list with no limit whole or refuses it as too large, and pages with limit. Its first page request is paced once, and a 512-row page is too large, so the client halves it.
    • The link connects, and the list published before Connected has all 500 rows, each once.
    • Red at the test commit (the old handshake): the link never connects (the link connects instead of failing on a list too large: Elapsed). Green with the fix.
  • The other eight handshake tests pass, a_watch_list_too_large_with_tools_is_read_without_them among them.
  • cargo clippy -p acpmux --all-targets -- -D warnings is clean.
  • Through cmux-ci test with GHOSTTY_ZIG_SOURCE=ghostty-next (run 41135094111): every test in cargo test -p acpmux passes except the base red hermetic_daemon_guard and two load flakes that pass 3/3 alone (classification). The panic ratchet passes and no hard lint rose (run 41139205389).
  • Handshake time against an old peer: about 11 pages of 512 rows for the incident's list; each page costs that peer's pace about a tenth of a list, so the read adds about one pace window (at most 15 s) inside the 90 s handshake deadline.

Observed / Not run

Observed: the red and green above, run on this lane VM, plus a reading of the c230bb58a server (server/list_reply.rs sessions and watch, hub/session_pages.rs session_list_reply, hub/list_guard.rs pacing). That build pages _acpmux/watch and _acpmux/sessions by limit and cursor, and sends an oversized reply as message.too_large.

Not run yet: the live link to that Mac. After this lands, a Linux host that self-updates picks it up within about 15 minutes, and the check is that its link to the Mac connects and a PR owned there reads ALIVE in the PR-owner lookup.

Review

Independent Codex (gpt-6.1-sol, high): SHIP at 33dd14b7. It checked the fix against the c230bb58a server's limit and cursor paging and its pacing, the reader's buffer on error replies, and that the halving and pacing retries end.

Changelog

  • Fixed: a machine whose session list is too large for one message, on an older build, links to the others instead of staying unknown.

Agent session: agent session

🤖 Generated with Claude Code