cmux NEXT web: a refused acpmux offers Restart acpmux, and the pane stops retrying alone
Leo: "also lawrence ran into this for some reason"
claude-sr2, word for word: "#24420, then #24421: a refused acpmux offers Restart. Lawrence's 34a5ea4 on base already names the binary differently, so rebase onto it and keep only the Restart part. Don't rework his code. If the two conflict in substance, tell me and I'll take it to Leo."
The web half of #24420. Adopted from a lane that died on cmux16, rebased onto current feat-cmux-next, and cut down to the Restart part only.
What changes
- When the host's refused handshake carries
details.restartAcpmux(#24420: another binary's acpmux holds this app's socket, and this app may replace it), the host error card shows Restart acpmux beside Retry, with a hint that says what it does. The click callsacpmux.restart; the app stops that daemon (its chats are kept) and the pane asks for one handshake, which starts this app's own acpmux. - While the restart runs, no handshake starts:
AcpmuxRestartGate(hostRetry.ts) drops the pending automatic retry and a Retry queued behind an attempt in flight, andconnectHostitself returns while the gate is closed (a timer already queued, or a reconnect scheduled by a lost connection, asks nothing). A second click does nothing. When it ends, one handshake runs unless a client connected meanwhile. - New strings
host.restart,host.restarting,host.restartHint, in all 21 locales. - Gallery:
agent-pane.host-errorgainsrestart(with a click play) andrestart-unnamed.
What the rebase dropped, so base's code stays as it is: the earlier version stopped retrying after three refusals and said so (host.restartStopped). Base's refusal copy (#24795, after Lawrence's 34a5ea4) says "It will reconnect when that service is ready", so the pane keeps its automatic retries and base's copy is unchanged. Without the host's offer, the card is exactly base's.
Before / after (gallery, at the head; before is base's HostError with the same message)
| Before | After | |
|---|---|---|
| refusal naming an app, dark, after the click | ![]() | ![]() |
| unnamed refusal, light | ![]() | ![]() |
Captured with scripts/gallery-matrix/runner.ts (Chromium) on the fast-checks Blacksmith job, run 40248657116, at f9c724c9a9. The head since then is a rebase onto #24420's squash plus one dependency-list line; HostError.tsx, HostError.gallery.tsx, styles.css and hostRetry.ts are byte-identical at the head 0745eb45a1, so the captures stand.
Tests
bun test src/agent-session/acpmux/hostRetry.test.ts src/agent-session/acpmux/HostError.test.tsx on the fast-checks job:
- red at f60451b8d8 (the gate tests before
AcpmuxRestartGateexisted): 3 pass, 1 fail, 1 error (run 40247873874); - green at e2c07b5a3e: 8 pass, 0 fail;
tsc --noEmitreports nothing in the changed files. - green at 11d35a04a5 (after the rebase onto #24420's squash 9b358d16d4): 8 pass, 0 fail (run 40258006321); green again at the head 0745eb45a1 (rebased onto 41993a1940): 8 pass, 0 fail (run 40259954170).
bun run checkthere: no lint finding of this PR's; the 4 lint warnings left are base's own (fast gates: "4 finding(s) the base has too").
Review
Codex gpt-6-astra xhigh, three rounds (independent session, read-only). Round 1 BLOCK (a Retry queued before the restart still ran mid-shutdown), round 2 BLOCK (timer and close callbacks still reached connectHost), both fixed with tests above. Round 3: SHIP at e2c07b5a3efd5fd4b829260f6869ea184bbea77c. Fast gates' web lint then flagged restartGate missing from the connect effect's dependencies; adding it (a useState value made once, so the effect never reruns for it) got SHIP at fba61a5b58. After the rebase onto #24420 (only Localizable.xcstrings conflicted: base's file plus this PR's three keys), a fifth round checked the per-file change lines are identical, the xcstrings holds base's keys unchanged plus the three, and that base's changes add no connectHost/retryHost path around the gate: SHIP at 11d35a04a5b9b3ff04f5df6dbefc74d3669ec48e. Base then moved again (#25181 also prepended to the xcstrings), so the three keys now sit just before host.retrying instead of at the top; a sixth round checked the same things: SHIP at 0745eb45a1dd426ec58f775d63bdef36c6e6b975.
Timers: no new timer or observer. The existing retry timer is cleared on restart and on unmount, as before.
Changelog
- Fixed: a chat refused because another acpmux holds the socket offers Restart acpmux, which replaces it with this app's own.
Agent session: big-red
🤖 Generated with Claude Code



