cmux NEXT: restart a stale acpmux on the app's build at once; Settings > Updates shows its build

Closed
TE
10d ago
+4020-222

Waiting on #23921 and #23199. Codex xhigh review: SHIP at 44ac61e8. The automatic continuation of a turn this restart interrupts is #23921's (acpmux-unify), so this merges after it. This PR never sends endAgents; without #23921 an interrupted turn comes back as outcome_unknown and resumes on the next prompt.

Summary

Leo: "OUR SHIT IS HARDLY EVER GOING TO GO IDLE. IT JUST WONT. YOU'LL JUST HAVE TO ACCEPT IT BREAKING AT SOME POINT BUT IT RESUMES WHATEVER IT INTERRUPTED. END OF STORY FOR THE DAEMON UPDATE."

Leo: "how do we get cmux next users (esp just the team rn) to update to the latest acpmux daemon!?"

Stacked on #23199: this PR is git diff 19c5c3be 44ac61e8 (3d3d7f7c, 4899a06c, 4de171b9, e243fc9d, 44ac61e8, plus a base merge). Once #23199 merges, this branch is rebased onto feat-cmux-next.

  • At launch and at every reconnect, an acpmux on another build than the app's bundle (or one running from a bundle the update replaced) is now shut down at once. This happens whether or not a turn is running, and whether or not it hosts agents. The shutdown sends no endAgents, so hosted agents keep running, and the bundled acpmux that starts next adopts them. The rest of the handoff is unchanged: wait for exit, check health, retry, and act once per build key.
  • agent.replaceStaleAcpmuxWhenQuiet (Debug Settings) now defaults to off. Turning it on brings back the quiet wait with its 10-minute cap.
  • A daemon without agent hosts is restarted too: its agents end, and each chat's session, log and resume id stay on disk. #23921 continues the interrupted turn by itself; until it lands, the next prompt resumes the chat. That is the break Leo accepts above.
  • One case is still kept: a daemon with an in-memory store and no agent hosts. Nothing on disk could resume its chats, and it was never restarted before this change either.
  • A stray ~/.local/bin/acpmux (no cmux install runs it) without agent hosts is taken over the same way, its launchd job booted out first so KeepAlive doesn't start it again. Before, it was kept by the stray check and then shut down anyway by the build check, without the bootout.
  • Settings > Updates shows the daemon line in the Channels card: "acpmux daemon: (up to date)", "(restarting…)", "(not the app's build)" or "Not running". It says restarting only when a lookup will actually restart it (another build or a replaced bundle, and this launch's handoff not used up); a daemon the app keeps (a memory store without agent hosts, or one whose shutdown failed) reads "not the app's build". cmux.settings.channels.state gets a daemon {title, value} field, which the app fills through an optional closure on ChannelsPageProvider.

Leo-ask: ask-48bbaec1d4
Leo-ask: ask-061e3574a6
Leo-ask: ask-e8a08a017c

Changelog

  • Changed: cmux NEXT replaces an acpmux of an older build as soon as the app starts, without waiting for agents to go idle; agents keep running and pick up where they stopped. Settings > Updates shows the acpmux daemon's build. (Highlight: release-notes/next/highlights/agent-daemon-updates-at-once.md.)

Tests

  • decide(): with the toggle off, a stale daemon restarts whether busy or not, and whether or not it hosts agents. With the toggle on, the old wait still applies.
  • Coordinator, against a stand-in old daemon: an old daemon plus a new bundle while a turn runs gives .stopped at once, with and without agent hosts. The stand-in records the shutdown's endAgents parameter, and the test checks that it was not sent.
  • A full launch with the toggle off restarts the busy old daemon and starts the bundled one, and the summary goes from restarting to up to date.
  • AcpmuxDaemonSummaryTests: each state, including a replaced bundle (restarting) and another build no lookup hands off (not the app's build); the quiet-wait toggle defaults off. A bun test covers the card line.
  • aShutdownThatFailsDuringASettingsReadIsNotTheAppsBuild: a shutdown that fails while Settings is reading leaves the card at "not the app's build", not "restarting…" (red before 44ac61e8).
  • aMemoryStoreWithoutAgentHostsIsNeverHandedOff: handsOff says no for a memory store without agent hosts and yes with agent hosts or a disk store; after a handoff the build's key is used up and handsOff says no.

Observed / Not run

Observed: on cmux16, AcpmuxQuietHandoffTests and AcpmuxDaemonSummaryTests pass, 23 of 23. Base 4de171b has an unrelated compile red in RightSidebarSectionBackgroundTests.swift:12 (from #24056), patched only in that scratch copy to get the target to build.

Not run: no builds on this Mac, and no live daemon was restarted. The live adoption of hosted agents was not run; the tests check that the shutdown omits endAgents.

Run: python3 scripts/cmux-next/prepush-gate.py (no new violations), scripts/web-lint.sh (pass), ./scripts/localize-changes (strict validator passed), the What's New digest check on the highlight (no problems).

No capture: a text line in the webview Channels card (Settings > Updates). mockChannels.ts carries it for the gallery run.

Agent session: Air-Blue

🤖 Generated with Claude Code