cmux NEXT daemon update: a toast when a machine's update fails
Part 8 of the rebuild of #23199 and #24070 (adopted by hb-acpmux). On feat-cmux-next. #25393 (below it) landed as c743a343, and this branch is rebased onto it (head 9f2c7d99, the same patch-ids as the reviewed 6a05d000; +415 ā1, of which 262 are the two strings' translations).
When the daemon update records a failed or rolled-back update of a machine (once per machine and build), a toast in the front window says so and stays until closed: "Couldn't update : " or " kept its previous build: ".
Leo: "BRO CANT YOU JUST LIKE SUSPEND SHIT AFTER A TOOL CALL/TURN THEN JUST GO AND AUTOUPDATE OUR TEAMMATES NEED ACPMUX UPDATING TOO..."
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-ask: ask-48bbaec1d4
Leo-ask: ask-061e3574a6
Change
From #24070 at bd4892c8: DaemonUpdateBoard.apply shows notice(_:) for an event that asks for one (notify, set by the queue only when it records a failure), through CmuxToastCenter with CmuxToast.untilClosed. Two translated strings.
The toast goes to a workspace window (WindowKind.main). It is the window that the key window, the main window or the frontmost window stands for (a sheet, the palette or a panel stands for the window it is over). A workspace window in view on the active Space comes first; one on another Space, or minimized, gets the toast only when it is the only workspace window, since the toast stays until closed. Settings, Debug Settings and page windows never get it (DaemonUpdateBoard.noticeWindow).
Why: an update fails in the background, mostly while the app is not active, and AppKit then has no key or main window, so a toast drawn in keyWindow ?? mainWindow was lost. This was found while planning the capture. The first fallback, a window that can become main, failed its test (it is false for windows not yet shown). The second, the front non-panel window, passed its unit test but still showed nothing on the capture mini. Codex then flagged that it could land on Settings, a sheet or another Space.
Each toast's id names the target and the build the update was to (DaemonUpdateBoard.noticeToast). CmuxToastCenter replaces a toast with the same id, and keeps one quiet for two minutes after it is closed. So a later build's failure, or another machine with the same name and reason, gets a toast of its own; the same event again replaces its toast in place. This was a review fix: the id was the message hash.
A process with no application (a test of the policy with a real board) shows no toast. Base's aFailureShowsItsStateAndAsksForOneNotice crashed on the NSApp read until 6a05d000. An earlier fix tried windows that can become main, but that is false for windows not yet shown, so it failed the test.
No timers: the toast stays until closed.
Tests
aFailureTheBoardIsAskedToNoticeShowsOneToast: nothing for a state without the ask or for a non-failure; one toast each for a failure and a rollback, naming the machine and the reason. Red on base:showNoticedoesn't exist there.eachFailedTargetAndBuildIsAToastOfItsOwn: same message, but different ids for a later build and for another machine; the same id for the same event; a toast only for a failure. It needsnoticeToast, which doesn't exist before the fix.aNoticeWithTheAppInTheBackgroundGoesToItsFrontWindowandaNoticeGoesToAWorkspaceWindowInView: with no key or main window, the frontmost workspace window; a panel or sheet stands for its window; Settings as key gives the workspace behind it; a window in view before one on another Space; one on another Space when it is the only one; no workspace window, no toast. The background case was seen red on the fleet before the first fix (f448b6da, 2 issues: no window).
Observed / Not run
- Release compile at 9f2c7d99 on the Mac fleet (
cmux-ci compile-check --release --mac): arm64 passed (step eef1dd962046da0589a47df6, aws-m4pro-5, 464 s) and x86_64 passed (step 276a406464839b063dd8edc4, aws-m4pro-2, 441 s). The PR's earlier app compile (build fleet) at 6a05d000 was green too: https://github.com/manaflow-ai/cmux/actions/runs/40348107011/job/121660794196 - Fleet at 6a05d000:
DaemonUpdateTargetTests(10),DaemonUpdatePolicyTests(21) andAcpmuxPeerUpdateTargetTests(22) pass. Before 6a05d000,DaemonUpdatePolicyTestscrashed on theNSAppread. - PR CI
cmux-next swift testat 6a05d000: 19 reds, all on the known base and flaky list. - Codex gpt-6-astra xhigh: SHIP at e250be2d, and SHIP at 6a05d000 for the
NSAppfix.
Capture
Steps: debug.daemon_update {state: failed, notify: true} (#25481) with the DEV app not active on the capture mini, which is how an update fails in practice.
Before, at feat-cmux-next 8b5cafb9: no toast.
Capture steps-52a55cdb12 at 8b5cafb988ba (fleet build, capture mini, text checked for private data)
| Light | Dark |
|---|---|
![]() | ![]() |
After, at e250be2d: the toast "Couldn't update build-box: the machine ran out of disk space", with its close button, at the bottom of the workspace window.
Capture steps-52a55cdb12 at e250be2d20e5 (fleet build, capture mini, text checked for private data)
| Light | Dark |
|---|---|
![]() | ![]() |
The earlier head 3da53c5b showed no toast in this same capture, because its window choice (key, main, or front non-panel window) found nothing usable while the app was inactive. That is the bug the workspace-window rule fixes.
Agent session: big-red
š¤ Generated with Claude Code



