Repository navigation
cmux-next: local acpmux and cmux-tui move to the bundled build by themselves - #23199
teamleaderleo wants to merge 19 commits into
Conversation
|
Owned by hb-acpmux on big-red · session |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Passes: CI passes on CI passes on Written by red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 84d5d32 to compare; do not merge until green; see lane-rules Tour of |
This comment has been minimized.
This comment has been minimized.
|
Bead |
…off at a time From the review of #23199: - the unresponsive watch restarts each home at most 3 times per launch; - the quiet wait asks once more after its watch is on, so a turn end between the quiet check and the watch is not missed; - a recheck waits out lookups already under way instead of reusing their sessions read, and a start waits for a reconnect's lookup, so two handoffs never run at once; - two QuietDaemonUpdate tests wait for the state they assert on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
1 similar comment
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
codex-review: SHIP at a1432fe
|
…off at a time From the review of #23199: - the unresponsive watch restarts each home at most 3 times per launch; - the quiet wait asks once more after its watch is on, so a turn end between the quiet check and the watch is not missed; - a recheck waits out lookups already under way instead of reusing their sessions read, and a start waits for a reconnect's lookup, so two handoffs never run at once; - two QuietDaemonUpdate tests wait for the state they assert on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
a1432fe to
020c8bd
Compare
|
Review (independent subagent): SHIP at 020c8bd Rebase of a1432fe (reviewed SHIP) onto feat-cmux-next. The reviewer checked that all 34 files have the same changed lines as before the rebase. The one conflict, TunableCatalog.all in DebugSettingsService.swift, keeps every base entry and adds DaemonTunables.all once. No base commit adds a caller of an API this PR changes. |
|
|
…mselves After an app update (nightly, beta or release) the local daemons no longer stay on the old build: - acpmux: a daemon of another build is handed off at a quiet moment at every lookup, also one without agent hosts (its chats resume from disk). Busy waits on _acpmux/watch; a daemon with agent hosts waits at most 10 minutes. The next daemon must report the bundled build within 5 s (one retry, then the banner). - acpmux that stays alive but stops answering is restarted (3 missed status calls over 20 s, confirm, SIGTERM, SIGKILL after 10 s), once per pid; agent hosts survive and are adopted. - cmux-tui: QuietDaemonUpdate checks every handshake against the bundled build (read again, so an in-place update is noticed) and hands a stale daemon off with handOffForUpdate when the person, the owner and the app are quiet; never forced, toast after 30 minutes, 90 s health check with one retry. - cmux-tui owner that stays stuck for 30 s (2+ stuck ensures) is replaced without the confirm, once per owner process; server.busy is never replaced. Each behind a default-on Debug toggle (agent.replaceStaleAcpmuxWhenQuiet, agent.restartUnresponsiveAcpmux, cmuxTUI.restartStaleWhenQuiet, cmuxTUI.restartUnresponsive). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
…off at a time From the review of #23199: - the unresponsive watch restarts each home at most 3 times per launch; - the quiet wait asks once more after its watch is on, so a turn end between the quiet check and the watch is not missed; - a recheck waits out lookups already under way instead of reusing their sessions read, and a start waits for a reconnect's lookup, so two handoffs never run at once; - two QuietDaemonUpdate tests wait for the state they assert on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
A recheck that came during daemonReady reused the settled lookup and its old sessions read, so a stale daemon that went quiet stayed until an unrelated lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
The detached --version task returns nil when the process does not start; the closure needs its type for that (fast tier: 'nil' requires a contextual type). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
020c8bd to
ac9d19e
Compare
|
Review (independent subagent): SHIP at ac9d19e Second rebase of 020c8bd (reviewed SHIP) onto feat-cmux-next. Commits 2 to 5 are identical in the range-diff. Commit 1 differs only in TunableCatalog.all, which is base's list (now with BrowserPageTunables.all) plus DaemonTunables.all once. In all 34 files the PR's added and removed lines hash the same as before. No base code added since calls an API this PR changed or adds a type this PR adds. |
The quiet restart of a stale local cmux-tui never fired: the daemon's own agent screen detector (a cmux SDK client) holds two connections with no name and no kind for its whole life, and `.reconnecting` took every kindless local client for a `cmux` CLI call in flight. In the app the probe was never idle, so the restart waited until the overdue toast; QuietUpdateKeepsTerminalTests failed on it in CI and 3 of 3 times on cmux16 (client.list showed the two kindless clients, 30 s old). A kindless local client now holds the restart while it is younger than `callSeconds` (a minute; the quiet restart tries again each minute). The live test waits until the daemon's standing clients are older than its 10 s window, then proves a fresh CLI call still holds the restart and the terminal survives it. It reports what the probe saw when it fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
Under load the first try's task can start after the test sets the owner idle; it then restarts before the client-detached event the test means to drive it (seen once on cmux16: restarts [11] at line 110). The test counts owner probes and waits for the first one before it changes the owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
…r test requires its first probe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-06d5-7823-8372-148833c4be22&host=cosmic-gray
|
Review (independent subagent): SHIP at da84a7b On top of ac9d19e (reviewed SHIP). 4f7d43f fixes a real bug that CI's first test run found. OwnerIdleProbe counted the daemon's own agent-screen-detector connections (kindless and permanent) as CLI calls in flight, so the quiet restart could never fire. Now a kindless local client holds it only while connected_seconds < 60, which the reviewer checked against client.list's wire format (a decimal string). The trade-off: a kindless call older than a minute, such as cmux agents wait, gets a retryable error at the handoff; terminals survive. c36a5ec and da84a7b make the busy-owner test wait for, and require, its first probe, and add the doc cross-references. On cmux16, QuietUpdateKeepsTerminalTests passed 3/3 and QuietDaemonUpdateTests 5/5. |
|
Found 11 test failures on Blacksmith runners: Failures
...and 1 more test failures. View all on Blacksmith
|
|
|
|
|
|
|
|
|
|
|
…t wired yet) AcpmuxUnresponsiveWatch counts status calls that miss their deadline and stops the daemon only it found (pid, start time, audit token). This commit does not call it from the lookup yet, so AcpmuxUnresponsiveRestartTests fail: a daemon that is alive but never answers keeps its lock and no new daemon starts. Rebuilt from #23199 (daemon-autoupdate 05367c0) on current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not wired yet) UnresponsiveOwnerWatch reads every `server ensure` outcome and, after two stuck ensures over 30 s, replaces the owner with `--replace-unresponsive` (once per owner process, three times per launch, never a `server.busy` owner). This commit does not attach it to the launcher yet, so DaemonAutoRestartTests.aStuckOwnerIsReplacedWithNoConfirmAfterTheWindow fails: only the Restart button replaces a stuck owner. Rebuilt from #23199 (daemon-autoupdate 05367c0) on current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Cloud machine becomes a DaemonUpdateTarget: the same `cmux link machine update` as Update Machine, with `--to-bundled`, so a machine on any other build than the app's gets the app's own pinned build. It waits for a quiet moment (no person typing, no chat or terminal UI attached, no `cmux` call in flight, no terminal agent or chat in a turn) and the queue updates it mid-turn once it stayed busy 10 minutes; a machine whose daemon the app refused updates at once. cloud.updateMachines (Debug Settings, next to agent.updatePeers) turns it off. Nothing offers these targets yet: the app's handshake follower and the header line come next. The quiet read's client rule now leaves out a kindless local client connected for over a minute (the daemon's own agent screen detector), as the review of #23199 found for the local probe; #24070's rule counted it and a machine with one was never quiet. From #24070 (daemon-peer-update bd4892c). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t wired yet) AcpmuxUnresponsiveWatch counts status calls that miss their deadline and stops the daemon only it found (pid, start time, audit token). This commit does not call it from the lookup yet, so AcpmuxUnresponsiveRestartTests fail: a daemon that is alive but never answers keeps its lock and no new daemon starts. Rebuilt from #23199 (daemon-autoupdate 05367c0) on current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Cloud machine becomes a DaemonUpdateTarget: the same `cmux link machine update` as Update Machine, with `--to-bundled`, so a machine on any other build than the app's gets the app's own pinned build. It waits for a quiet moment (no person typing, no chat or terminal UI attached, no `cmux` call in flight, no terminal agent or chat in a turn) and the queue updates it mid-turn once it stayed busy 10 minutes; a machine whose daemon the app refused updates at once. cloud.updateMachines (Debug Settings, next to agent.updatePeers) turns it off. Nothing offers these targets yet: the app's handshake follower and the header line come next. The quiet read's client rule now leaves out a kindless local client connected for over a minute (the daemon's own agent screen detector), as the review of #23199 found for the local probe; #24070's rule counted it and a machine with one was never quiet. From #24070 (daemon-peer-update bd4892c). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t wired yet) AcpmuxUnresponsiveWatch counts status calls that miss their deadline and stops the daemon only it found (pid, start time, audit token). This commit does not call it from the lookup yet, so AcpmuxUnresponsiveRestartTests fail: a daemon that is alive but never answers keeps its lock and no new daemon starts. Rebuilt from #23199 (daemon-autoupdate 05367c0) on current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…swering (#25379) * cmux NEXT acpmux watchdog: tests for a hung daemon, and the watch (not wired yet) AcpmuxUnresponsiveWatch counts status calls that miss their deadline and stops the daemon only it found (pid, start time, audit token). This commit does not call it from the lookup yet, so AcpmuxUnresponsiveRestartTests fail: a daemon that is alive but never answers keeps its lock and no new daemon starts. Rebuilt from #23199 (daemon-autoupdate 05367c0) on current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT acpmux watchdog: a stuck handshake signals the hung daemon and starts a new one When the daemon accepts the connection but never answers within the deadline, the host asks the unresponsive watch to restart it: SIGTERM to the hung pid (SIGKILL after its grace), then a fresh daemon, and the interrupted turn resumes on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT acpmux watchdog: tests for an answer during the confirm, the toggle off before the claim, a lookup with it off Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT acpmux watchdog: claim only a streak that still stands, read the toggle at each miss and before the claim A claim needs the streak that made the restart due: an answer meanwhile (another lookup) ends it, so a daemon that recovered is never signalled. The watch follows this app's daemon also with the toggle off, so answers always end a streak; the toggle is read at each miss (off ends the streak) and again right before the claim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT acpmux watchdog: a claim names the streak that made the restart due An answer during one lookup's confirm, then fresh misses, begin a new streak; the old confirm's claim names its own streak, so it neither takes the new one before its window nor removes it. aDaemonThatAnsweredMeanwhileIsNotClaimed covers answer, new misses, old claim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ut never answers (#25382) * cmux NEXT cmux-tui watchdog: tests for a stuck owner, and the watch (not wired yet) UnresponsiveOwnerWatch reads every `server ensure` outcome and, after two stuck ensures over 30 s, replaces the owner with `--replace-unresponsive` (once per owner process, three times per launch, never a `server.busy` owner). This commit does not attach it to the launcher yet, so DaemonAutoRestartTests.aStuckOwnerIsReplacedWithNoConfirmAfterTheWindow fails: only the Restart button replaces a stuck owner. Rebuilt from #23199 (daemon-autoupdate 05367c0) on current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT cmux-tui watchdog: replace a stuck owner with no confirm DaemonRecovery follows the launcher's ensure outcomes with UnresponsiveOwnerWatch: once the owner stayed alive but unanswering for the sustained window (30 s, two stuck ensures), the app runs the button's restart without its confirm, at most three times per launch, while cmuxTUI.restartUnresponsive is on. aStuckOwnerIsReplacedWithNoConfirmAfterTheWindow passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT cmux-tui watchdog: stop with the service, claim before replacing, no window while off Review of ddf4bb5 (Codex): - The watch outlived DaemonService.shutdownConnection and could replace an owner after it; shutdownConnection now detaches it (UnresponsiveOwnerWatch.stop: the deadline is cancelled and nothing is claimed after). - A replace queued while the owner was stuck ran even after the toggle went off, an ensure worked or the person's Restart ran; the replacer now claims it on the main actor right before restarting, and a claim that says no gives the replace back. - The deadline was scheduled and cancelled after the state lock, so an older outcome could cancel a newer streak's deadline; both now happen under the lock. - With the toggle off stuck ensures still built a streak and a deadline; now a stuck ensure with the toggle off ends the streak and cancels it, and a deadline that fires with the toggle off ends the streak. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT cmux-tui watchdog: tests for a refused replace, the toggle off between ensures, a claim that finds it off Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT cmux-tui watchdog: give back a replace that never claimed, follow the toggle as it changes A replace the recovery refused before its claim (the person's Restart was running) is given back like a claim that says no, so the same owner, still stuck, is replaced later. The toggle's changes reach the watch through an ObservationStream (cancelled by the next attach and by detach): off ends the streak and its deadline at once, and a claim that finds it off ends the streak too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT cmux-tui watchdog: the toggle is read, not followed ObservationStream coalesces a toggle turned off and on again into no change, so following it could not promise more than reading it. The window measures the owner: the toggle is read at each stuck ensure, when the window passes and at the claim, off at any of them ends the streak, and the replace runs only when the claim on the main actor reads it on. The follow task is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT cmux-tui watchdog: the claim closure takes its own weak reference The nested closure referred to the Task's captured weak self, which Swift 6 refuses in concurrently-executing code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Closing: rebuilt on current feat-cmux-next as smaller PRs, now merged:
Base already moves a stale local acpmux and cmux-tui to the bundled build ( |
…er from restarting (#25513) * OwnerRestartWaitsForChatTests: a CLI call without a kind holds the Chief owner restart (red) The DEV keyless Chief owner restart (e859e33) probes the owner with OwnerIdleProbe, which counts a fresh local client that names no kind as idle. A `cmux` CLI or MCP call names no kind, so the owner restarts in the middle of its request. This test opens such a call and expects the restart to wait while it is connected and to run after it ends. It fails on this commit: attempt() returns .started. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a * OwnerIdleProbe: a fresh CLI or MCP call without a kind keeps the Chief owner busy The DEV keyless Chief owner restart (e859e33, c6390e5, 79ce2f2) asked OwnerIdleProbe whether the owner is idle, and the probe let every local client without a kind through. A `cmux` CLI or MCP call names no kind, so IdleOwnerRestart could restart the owner in the middle of its request. As in 4f7d43f (the quiet restart's fix on the closed #23199 branch), a local client without a kind now holds the restart while it is younger than callSeconds (a minute). An older one is a standing connection: the daemon's own agent screen detector holds two for its whole life and must not hold the restart forever. Such a client stops counting as a call without any owner event, so when the probe sees only calls in flight it answers callInFlight(seconds:) and IdleOwnerRestart schedules one more attempt for then (a one-shot DemandTimer, cancelled when the outcome is decided and with the actor). A call that ends sooner sends client-detached, which probes again as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a * OwnerRestartWaitsForChatTests: the CLI-call test waits for the new owner before it shuts down Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Status at 05367c0: Codex (gpt xhigh) passes this head. The acpmux watchdog sends its signal by the daemon's audit token (
proc_signal_with_audittoken, looked up at run time because macOS 14.0 and 14.1 lack it), so a pid handed to another process is never signalled however long the app stalls.Leo-ask: ask-98502f19a4
Leo-ask: ask-48bbaec1d4
Leo-ask: ask-e8a08a017c
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."
So no wait here is open-ended: a stale cmux-tui waits at most 2 minutes for a quiet moment and a stale acpmux with agent hosts at most 10 minutes, then each is handed off between requests. #24026 then restarts a stale local acpmux at once and shows its build in Settings > Updates.
Summary
After a cmux NEXT update (nightly, beta or release), the app's own daemons could stay on the old build. A local acpmux with no agent hosts was kept running indefinitely ("its agents would end"). A cmux-tui found stale after launch waited for the manual Restart button. A daemon that was alive but hung had to be restarted by hand. This PR is the local-app half of keeping every daemon on the bundled build:
_acpmux/watch. A daemon with agent hosts waits at most 10 minutes; the wait races a deadline on that cap, so a long turn that only streams output can't hold it past the cap.proc_pidinfo), checked again right before each signal and only after the kernel exit watch is on the pid. A pid reused by another process counts as the daemon's exit and is never signalled.QuietDaemonUpdatechecks every handshake against the bundled build. The build is read again each time, so an in-place app update is noticed. A stale daemon is handed off with the existing losslesshandOffForUpdateonce quiet:cmux chiefchat exits (code 3) and a CLI or MCP call in flight fails, and both can be run again.server ensureresults over 30 s, the owner is replaced (--replace-unresponsive) with no confirm. This happens once per owner process and at most 3 times per launch. An owner answeringserver.busyis never replaced.Each behaviour has a default-on Debug Settings toggle:
agent.replaceStaleAcpmuxWhenQuiet,agent.restartUnresponsiveAcpmux,cmuxTUI.restartStaleWhenQuiet,cmuxTUI.restartUnresponsive. The rules are written up inplans/cmux-next/server-updates-DESIGN.md(rule 2a/2b and the status table).Coming next, in separate PRs:
acpmux host updatewith rollback, idle check, arch check and migration of hand-written units;Testing
Tests added:
AcpmuxQuietHandoffTests: a stale daemon without agent hosts, busy then quiet, the agent-host cap, the health check with retry and banner;aBusyDaemonWithAgentHostsIsHandedOffAtTheCapWithNoEvent(the cap fires with no event and no reconnect, and not before).AcpmuxUnresponsiveWatchTests: the miss window, confirm, once per pid, toggle off.AcpmuxUnresponsiveIdentityTests: a reused pid is never signalled; a daemon that exits after its identity check gets no signal (aDaemonThatExitsAfterItsCheckGetsNoSignal); a signal whose token names an earlier process with the pid is refused by the kernel (aSignalForAnotherProcessWithThePidIsRefused).UnresponsiveOwnerWatchTests,IdleOwnerRestartRetryTests.QuietUpdateKeepsTerminalTests: a live cmux-tui in a temp session. The quiet restart waits for a CLI call and the terminal keeps running.QuietDaemonUpdateTests: covers an old daemon next to an app updated while running, a successor of the wrong build or one that never connects,anAttachedClientBetweenTurnsHoldsTheRestartOnlyUntilTheCapandpastTheCapARequestInFlightStillDefersIt.DaemonAutoRestartTests, plusServerUpdateNoticeTests.anotherBuildWithNothingMissingIsAPlainNotice.Observed / Not run
swift build --build-testsof the package passes at the new head:QuietUpdateKeepsTerminalTestspassed 3/3 andQuietDaemonUpdateTests5/5 (at 4f7d43f);AcpmuxQuietHandoffTests,AcpmuxQuietHandoffDecisionTests,AcpmuxUnresponsiveWatchTests,AcpmuxUnresponsiveRestartTestsand base'sAcpmuxStrayDaemonTestspassed 3/3.ws://URL, which the launcher refuses./usr/bin/nc, so the copy is now signed ad hoc.UnresponsiveOwnerWatchTests,IdleOwnerRestartRetryTests,QuietUpdateKeepsTerminalTestsandOwnerRestartWaitsForChatTestspassed in the daemon-tests job.ClassicUpgradeSimulationTestswas not run there: the build guard refused it for low disk, so CI's app-tests job is its run.aSignalForAnotherProcessWithThePidIsRefusedexpects the kernel's refusal, so it would fail on macOS 14.0 or 14.1, where the token call is missing and nothing is sent; no CI runner uses those versions.No capture: there's no visible UI to show. The change is daemon upkeep (handoffs, restarts and watchdogs) behind the Debug toggles.
Changelog
Changed: cmux NEXT moves its local acpmux and cmux-tui daemons to the app's own build after an update, at a quiet moment or after at most a few minutes, and restarts either one if it stops answering
Proof
No UI change beyond the existing toasts and banner; behaviour is covered by the tests above.
Checklist
Agent session: Air-Blue
🤖 Generated with Claude Code