Skip to content

cmux NEXT: never offer the running build as Update Ready - #22804

Merged
teamleaderleo merged 2 commits into
feat-cmux-nextfrom
cmux/update-no-repeat-offer
Oct 21, 2026
Merged

teamleaderleo merged 2 commits into
feat-cmux-nextfrom
cmux/update-no-repeat-offer

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Right after NIGHTLY updated itself and relaunched (Leo, 2026-10-20 18:55), the sidebar showed "Update Ready / Automatic Updates / Restart to Update" again, with no version line under the title. Leo's rule: updates just install, and cmux never offers the build that is already running.

What was wrong

Two gaps let the card show for a build that is not newer than the running one:

  1. Sparkle's silent install never told cmux which build it staged. With SUAutomaticallyUpdate on (the NIGHTLY defaults have it), Sparkle's own automatic driver stages an update through updater(_:willInstallUpdateOnQuit:immediateInstallationBlock:). UpdateDriver turned that into "Restart to Complete Update" but kept no item and cleared the detected version (UpdateDriver+SPUUpdaterDelegate.swift:58-67). UpdaterService.indicatorPhase (UpdaterService+Indicator.swift:13) then read stagedUpdate == nil and detectedUpdateVersion == nil, giving .ready(version: nil). That is a card with no version line, which matches the screenshot.
  2. Nothing compared a staged update with the running build. UpdateIndicatorPhase(_:version:) maps every auto-update .installing state to .ready, and UpdateFlow.footerPill and readyCard show any .ready. Sparkle can come back with an update for the build that now runs. After a relaunch, SPUUpdater probes for an installer that is still running (probeInstallerInProgressForHostBundleIdentifier) and, if it finds one, resumes it (resumeInstallingUpdate). It can also resume a download it kept (_resumableUpdate). In both cases it reports the installer's own item, so the build just installed came back as Update Ready.

Fix

  • UpdateDriver keeps the item Sparkle passes to willInstallUpdateOnQuit (quitInstallItem). stagedUpdate returns it while that "Restart to Complete Update" state holds, so the card gets its version, notes and build. The item is cleared when the update installs or a new download starts.
  • UpdaterService.indicatorPhase drops a .ready phase unless offersStagedUpdate(version:) passes. That check compares the staged sparkle:version with the running CFBundleVersion (SUStandardVersionComparator); with no build it falls back to "display version differs from the running one"; with neither it returns false. The flow, the card, the footer pill and the circle all read this phase, so none of them shows a build that equals or is older than the running one, or one whose version is unknown.

Changelog

  • Fixed: After an update relaunches cmux, the sidebar no longer offers "Update Ready" again for the build that is already running.

Tests

Fleet runs (cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite ..., Xcode 26.6):

  • CmuxUpdater, BackgroundInstallTests:
    • Red on 970e874: sparklesSilentInstallNamesTheStagedUpdate fails (stagedUpdate?.versionString → nil); the other 12 pass.
    • Green on 59a5f12: 13/13 pass. UpdateControllerPipelineTests and UpdateStateModelTests also pass.
  • CmuxNext, UpdateReadyNotNewerTests:
    • Red on 970e874 plus cmux-next palette: missing return in familyRow (base compile red since #22764) #22821's one-line palette fix: 4 of 5 fail, with 10 issues. The card shows "Update Ready" with detail "1.0.0" for the running build, "0.9.9" for an older one, and nil for an unknown one, and flow.phase stays .ready("1.0.0"). The newer-build control passes.
    • Green on 59a5f12 plus the same palette fix: 5/5 pass. SilentBackgroundUpdateTests, UpdateLoadingIndicatorTests, UpdateCheckFeedbackTests, UpdatedCardCurrentBuildNotesTests and UpdateReadyCardLayoutTests also pass (41 tests in all).

CmuxNext does not compile on base bc71d0f (RegistryPaletteProvider+Families.swift:58: missing return, from #22764; the fix is open as #22821). The CmuxNext runs therefore used throwaway commits that add only that one-line return on top of each commit here. Those scratch branches are already deleted. Until #22821 lands, this PR's own CmuxNext compile is red for the same base reason.

Observed / Not run

  • Observed: the fleet red and green runs above. python3 scripts/cmux-next/prepush-gate.py passed on both commits. I read Sparkle 2.10's SPUUpdater, SPUBasicUpdateDriver, SPUUIBasedUpdateDriver and SPUAutomaticUpdateDriver sources to trace the resume and silent-install paths.
  • Not run: no local build. I did not reproduce the relaunch on a real NIGHTLY, and Leo's cmux update log from 18:55 was not read. So which Sparkle path produced his card (a resumed installer, a kept download, or a genuinely newer nightly published a minute later) is inferred, not confirmed. If it was a newer nightly, the card is correct, and with this fix it now shows that version.
  • Not changed: on a resume, Sparkle may still ask cmux's driver to install the same build again; this PR only keeps it from being offered.

🤖 Generated with Claude Code

Agent session: cosmic-gray

teamleaderleo and others added 2 commits October 20, 2026 19:26
…e Ready

Sparkle's silent driver staged through willInstallUpdateOnQuit without telling
the host which item, so the card read .ready(version: nil) and nothing compared
it with the running build. The driver now keeps that item as the staged update,
and the indicator phase drops a staged update whose build is not newer than
CFBundleVersion, or whose version is unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c01-a42d-7970-a4a7-2f0ac203c940&host=cosmic-gray
@teamleaderleo teamleaderleo added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:normal Risk tier (docs/ci/risk-tiers.md) labels Oct 21, 2026
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 59a5f12: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr22804 -f ref=cmux/update-no-repeat-offer -f platform=macos -f backend_mode=local

Not verified: macOS jobs did not run: compile and app tests were skipped on 59a5f12734; ci-status is green without them.

CI passes on 59a5f12734 (run 39533275270 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-i6lgo created for this PR by the cmux coordinator.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

owner: 01a15c01-a42d-7970-a4a7-2f0ac203c940

@teamleaderleo
teamleaderleo marked this pull request as ready for review October 21, 2026 02:56
@cursor

cursor Bot commented Oct 21, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@teamleaderleo
teamleaderleo merged commit 7c01e3c into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the cmux/update-no-repeat-offer branch October 21, 2026 03:00
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…er a newer version

The Debug Settings "Simulate pending update" switch and debug.updater
{action: "simulate"} move from the App target into a new CmuxNextUpdater
type, UpdateSimulation. The App passes the switch to UpdaterService.attach,
which follows it, and DebugUpdater's stage and simulate actions are thin
calls (UpdateSimulation.stageSample, UpdateSimulation.simulate).
CmuxNextApp source lines no longer grow over feat-cmux-next (95249 at
base and merged), and neither the UpdaterService nor the AppServices type
grows (check-no-godfiles with --base passes on the merged tree).

The simulation now stages a version that is not the running one: the
notes' own when they are a newer build, else the pretend 1.99.0, so the
gate that never offers the running build (#22804) still shows the card on
the newest nightly; the real notes stay. Turning the switch off cancels a
simulation still reading its notes. Builds of one day order by number.

A click that cannot open the preview no longer leaves it pinned, and
Control-click on the card opens its context menu again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-663f-7650-b9f7-64d198c6b88b&host=cosmic-gray
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…e, click-to-pin preview, Simulate pending update (#22688)

* cmux NEXT: Update Ready card gets a themed switch, a primary Restart to Update, a click-to-pin preview and Debug Settings Simulate pending update

Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=e8f63fd9-94e8-4878-bcd4-704659aba1f2&host=cosmic-gray

* Update Ready card: simulate pending update in the updater module, under a newer version

The Debug Settings "Simulate pending update" switch and debug.updater
{action: "simulate"} move from the App target into a new CmuxNextUpdater
type, UpdateSimulation. The App passes the switch to UpdaterService.attach,
which follows it, and DebugUpdater's stage and simulate actions are thin
calls (UpdateSimulation.stageSample, UpdateSimulation.simulate).
CmuxNextApp source lines no longer grow over feat-cmux-next (95249 at
base and merged), and neither the UpdaterService nor the AppServices type
grows (check-no-godfiles with --base passes on the merged tree).

The simulation now stages a version that is not the running one: the
notes' own when they are a newer build, else the pretend 1.99.0, so the
gate that never offers the running build (#22804) still shows the card on
the newest nightly; the real notes stay. Turning the switch off cancels a
simulation still reading its notes. Builds of one day order by number.

A click that cannot open the preview no longer leaves it pinned, and
Control-click on the card opens its context menu again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cfc-663f-7650-b9f7-64d198c6b88b&host=cosmic-gray

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant