Repository navigation
cmux NEXT updates: a staged update gives way to a newer build of the ring - #24183
Conversation
… (red) Leo: "how are the dev build hot swaps? like the update ready i want the latest dev builds to be able to show up". Sparkle never reads the feed while an update waits, so a Dev build published after the staged one waits for a restart. These tests expect a probe once per check interval (and on the person's check or ring pick) that swaps in a strictly newer build, never during an install, never more than once per interval. StagedUpdateRefresh is an inert skeleton here; the fix fills it in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
Leo: "how are the dev build hot swaps? like the update ready i want the latest dev builds to be able to show up". Sparkle never reads the feed while an update waits (the held ready reply keeps its session open, so its scheduled check is skipped, and a check answers with the waiting update). While an update waits, StagedUpdateRefresh now probes the feed with the read-only probe, filtered by the ring as Sparkle filters, once per updates.checkIntervalSeconds, and at once when the person checks or picks a ring (follow(ring:) checks). A strictly newer build cancels the staged installer (Skip on the held reply: no skipped version, no user file touched) and starts Sparkle's background check, which downloads and stages the newer build. Never during an install: only Update Ready arms or runs it, a click to install ends it, and only an installer this app's driver holds is replaced (UpdateController.canCancelStagedUpdate), with downloads on. A build fresh enough that Sparkle would hold it for its deltas waits an interval. Stop condition: one one-shot DemandTimer, armed only in Update Ready with automatic checks on, cancelled when the phase leaves it; one probe at a time; the next a whole interval later. dropStaleStagedUpdate moves into the same type (staged-update upkeep), and the UpdateActionDelegate conformance into its own file, so the UpdaterService god type and file shrink instead of growing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
|
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)
red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 44fc3d3 to compare; do not merge until green; see lane-rules Tour of The path-selected recorder failed for core-after, core-before; see the run artifact and failed manifests. Path-selected interactions
Core UI (before): FAILED — 16 ok, 3 failed, 1 unavailable
Failed steps: 5. Split right Screenshots (12)1. Launch
Failed steps: 5. Split right Screenshots (12)1. Launch Tours are data in |
|
Owned by release-rings on huge-blue · session |
|
Found 9 test failures on Blacksmith runners: Failures
|
|
Bead |
… and schedule changes From the Codex review of #24183: - The swap's check is a foreground check through this app's driver (UpdateController.checkForUpdates), never Sparkle's silent driver, so the newer build is staged replaceable again for the next swap. - A probe of the old ring in flight when the person picks another ring is read again for the new ring, never taken as its answer. - A changed updates.checkAutomatically or checkIntervalSeconds replaces the pending probe deadline (off ends it; a new interval counts anew). - The delta wait matches Sparkle's: only a scheduled swap, only for a build that lists no sparkle:deltas yet (AppcastItem.listsDeltas); a check the person asked for takes the build at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
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. |
…behind (red) Codex re-review of #24183: a check while Update Ready left the flow's pending ask set, so a later check nobody asked for showed its progress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
The probe that answers a check while Update Ready shows clears the flow's pending ask when it keeps the staged build (UpdateFlow.askAnswered), so a later check nobody asked for stays silent. A swap the person started marks its Sparkle check as theirs (userCheckRunning), so another check joins it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
…(red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
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. |
|
Review (Codex gpt xhigh via acpmux on cmux16): SHIP at 89a3307 An independent reviewer from a different model, after four passes. Passes 1-3 at e304565, 9213fd4 and 178083f asked for changes; all are fixed on this head (see the PR body). The fleet |
…rings/staged-yields-to-newer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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. |
|
Review (Codex gpt xhigh via acpmux on cmux16, diff only): SHIP at dfcb302 A fresh independent review at the merged head. The merge of feat-cmux-next a3feffa (#24227) touched no updater source or test; the earlier SHIP was at 89a3307. The fleet |
|
|
|
|
|
|
|
|
|
|
…n Install click ends the staged-update probes Red on 501dca4: the away-and-back probe's old answer is taken, and a probe or the scheduled timer still cancels the installer after the click (review at 501dca4, gpt-6-sol). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
…l click ends the staged-update probes Ring picks are counted (ringMoved), so a probe that began before one is not taken as the ring's answer, even after away and back. A click on Install (the gate's install effect) cancels the pending probe, arms none, and a probe in flight cancels nothing, until Update Ready ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c1c-9365-71b3-b73b-dc961610d618&host=huge-blue
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. |
|
Review (independent, Codex gpt-6-sol, xhigh, diff-only on cmux16, no checkout): SHIP at e4bf66b The earlier review at 501dca4 (gpt-6-sol) raised two blocks. Both are fixed in e4bf66b, with regression tests in f96f943:
Verdict text: "No blocking findings. The generation counter, install gate, replaceability checks, timer cancellation, and race tests cover the requested failure modes. The remaining test gap is recovery when a replacement check fails without a phase transition." Observed
Not run
|
|
Shipped in:
|
































![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo-ask: ask-cd2e53c2ac (and ask-3d0b80c786)
Leo: "how are the dev build hot swaps? like the update ready i want the latest dev builds to be able to show up"
What was wrong
Sparkle never reads the feed while an update waits:
SPUUpdater.updaterTimerDidFirechecks_sessionInProgress).checkForUpdates()returned early with "check while an update waits: showing the staged update".follow(ring:), e.g. Nightly to Dev) also lands incheckForUpdates(), so a staged Nightly build stayed on the card. The ring switch did not reset a staged update.The result: once build N was staged, a newer Dev build N+1 never reached the Update Ready card until a restart.
What changes
StagedUpdateRefresh(new,CmuxNextUpdater). While Update Ready shows, it probes the feed with the existing read-only probe, which filters by the followed ring the way Sparkle'sallowedChannelsdoes. It probes:updates.checkIntervalSeconds(default 3600), andWhen the ring has a strictly newer build, it cancels the staged installer and calls
UpdateController.checkInBackground(). The cancel replies Skip to Sparkle's held ready reply, the same pathdropStaleStagedUpdatealready used. It records no skipped version and touches no user data. Sparkle then downloads the newer build and stages it, and the card shows it.Guards:
.ready,flow.installRequestedis checked, and the driver's cancel handle is gone once the install starts.UpdateController.canCancelStagedUpdatemust be true (this driver holds the installer), anddownloadsUpdatesInBackgroundmust be on. Sparkle's own silent install-on-quit and deferred downloads (Low Data Mode, metered link, managed channel) are left alone, and nothing is fetched for them.Stop condition and cleanup:
DemandTimeron the injected clock (ownerUpdaterService.stagedRefresh).updates.checkAutomaticallyon.UpdateProber,@concurrent).God-type ratchet.
dropStaleStagedUpdatemoves into the same type asdropStale(updater:), since both are staged-update upkeep. TheUpdateActionDelegateconformance moves to its own file. Both are pure moves, so the over-budgetUpdaterServicetype and file shrink instead of growing: 1881 to 1864 lines and 501 to 481.Coverage limits (read before dogfooding)
On the cmux NEXT nightly track,
installsUpdatesWhenIdleis on. A staged build normally goes straight to the idle install: the relaunch gate waits up toupdates.idleInstallMaxWaitSeconds, default 900 s. That wait is an install in progress, so this PR leaves it alone. The next build then arrives through the launch check after the relaunch.This PR covers a staged build that waits for a click:
updates.downloadAutomaticallywas off and the person clicked to download.It does not cover Sparkle's own silent install-on-quit (
SUAutomaticallyUpdateon, a preference shared with the legacy NIGHTLY app). Sparkle has no API to cancel that installer.No capture: no visible UI change; the existing Update Ready card shows whichever build Sparkle stages, and this only changes which build that is.
Tests
UpdateStagedYieldsToNewerTests(CmuxNextAppTests) uses a counting fake feed, aManualClockand stubbed Sparkle seams:Red commit d28e156 (an inert skeleton plus the tests), then the fix e304565. The Codex review then asked for four more fixes, each landed after its own red test where one could be written: 9213fd4 (the swap checks through this app's driver, never Sparkle's silent driver; a ring picked during a probe is read again; schedule changes replace the pending probe; the delta wait applies only to scheduled swaps of items with no
sparkle:deltas), red 834eb6b then 178083f, and red c3f1b2a then 89a3307 (a check while staged that keeps the build, or that starts no probe, ends the person's ask, so a later check nobody asked for stays silent).The same tests also cover: a fresh build that lists deltas swaps at once; a check the person asked for takes a fresh build at once; a ring picked during a probe reads the new ring, both ways; a schedule change replaces the pending probe.
If the newer build's download fails after the swap, nothing is staged until Sparkle's next scheduled check. Its timer restarts when the session ends, and that check stages the newest build again. The reviewer accepted this as non-blocking.
Command:
cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg=Packages/macOS/CmuxNext --arg=UpdateStagedYieldsToNewerTests --ref <sha>--red): queued on the fleet (the suites queue has been backed up for over an hour). I will post the result as a comment.Observed / Not run
compile-check --apppassed at every head (e304565, 9213fd4, 178083f, 89a3307). Codex (xhigh, on cmux16) reviewed four passes: CHANGES, CHANGES, CHANGES, then SHIP at 89a3307.prepush-gate.pyreported no new violations.check-concurrency.shpassed.check-no-godfiles.sh --base origin/feat-cmux-nexton the changed files passed; the type and the file both shrank. Fleet results are listed above.SPUUpdater.m(updaterTimerDidFireskips while a session is in progress;checkForUpdatesWithDriverresumes an installer in progress instead of reading the feed). That is what confirmed the bug.Risk: high (updater). This is not behind a default-off toggle, although docs/ci/risk-tiers.md says a user-noticeable high-tier change ships dark: Leo asked for this exact behavior, and only a newer signed build of the ring the person chose replaces a staged one. A revert is
gh-merge-green --revert. The PR swaps a downloaded update only for a strictly newer signed build of the same ring, through the existing cancel path.Changelog
Agent session: huge-blue
🤖 Generated with Claude Code