Skip to content

cmux NEXT updater: an install that left the old build running says why, instead of Restart to Update again - #31178

Merged
teamleaderleo merged 2 commits into
feat-cmux-nextfrom
lane/next-update-install-failed-says-why
Nov 1, 2026
Merged

teamleaderleo merged 2 commits into
feat-cmux-nextfrom
lane/next-update-install-failed-says-why

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Leo asked (2026-10-21): "Find out why the newest nightly is not on his Mac. Is nightly publishing stuck, is the cmux-next appcast stale, or did Sparkle download a build and fail to install it on relaunch (check the Sparkle logs and ~/Library/Caches)? Fix the root cause in the pipeline or updater. If an installed update ever fails, the app must say why instead of offering Restart again."

Leo-ask: ask-24cefc7e85

What I found

Change

  • UpdateInstallCheck: just before Sparkle relaunches (updaterWillRelaunchApplication), the updater saves {fromBuild, targetBuild, recordedAt} in the app's own defaults. The next launch reads that record once. If the app is still on fromBuild, the install failed. If it is on targetBuild, the install worked. A third build, a record older than a day, or a no-op install counts as neither. Every path where the install ends with the app still running clears the record (new abandonInstall()), so a later launch of the same build isn't reported as a failed install.

  • UpdateInstallFailure.diagnose: on a failed install, the app checks for causes it can see on disk:

    • App Translocation (a read-only copy)
    • a bundle or folder it can't write to
    • not enough free disk space

    If none of these applies, the card says the installer kept this version and gave no reason. Sparkle's installer leaves no error the app can read, so the app does not guess. The folder and space checks reuse the preflight's checks (UpdateInstallPreflight.placeFailure, split out of failure() with no change in behavior).

  • The staged update card, for the build whose install failed, now shows "Update Didn't Install", the one-line reason, and a Try Again button in place of "Restart to Update". The full reason and what to do are in the popover and in the button's help text. A newer staged build gets the normal card. While Try Again installs, the card shows Installing as before. The updater log records install of X did not replace Y: <cause>.

  • 10 new strings, translated into all 21 locales (non-English marked needs_review).

Tests

Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/UpdateInstallCheckTests.swift: judging the record, reading it only once, the relaunch writing it and the next launch reading it as failed, a relaunch into the new build counting as success, an abandoned install leaving no record, the card text for a failure and while installing, the reason applying only to the failed build, and diagnose (translocated path, a writable folder, a read-only folder).

Observed / Not run

  • Observed: scripts/cmux-next/prepush-gate.py reported no new violations. scripts/lint-ios-package-conventions.sh passed. The appcast reads quoted above.
  • Not run: the Swift compile and the suite. This lane VM has no Swift toolchain, so they run in this PR's CI. Not run: a real failed install in a DEV build (making the installer fail needs a read-only or translocated app on a Mac).

risk: high (updater path).

Changelog

  • Fixed: when an update fails to install after cmux restarts, the update card now says why and offers Try Again instead of offering Restart to Update again.

No capture: a native card state that needs a failed real install; the strings and layout reuse the existing staged update card.

🤖 Generated with Claude Code
Agent session: agent session


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

When Sparkle's installer fails to replace cmux after it quits, the app relaunches the old build and showed "Restart to Update" again as if nothing happened. The updater now records which build it installs over which before relaunch, checks the outcome on the next launch, and a failed install makes the update card say why and offer Try Again instead.

  • Saves a from/target build record just before Sparkle relaunches and reads it once at launch; still running the old build means the install failed.
  • Diagnoses the cause on disk: App Translocation, an unwritable bundle or folder, or not enough disk space; otherwise the card says the installer kept this version with no reason.
  • Any path where the install ends with the app still running clears the record, so a later launch doesn't report a false failure.

Written for commit 58c0cdd. Summary will update on new commits.

View guided diff Turn on auto-fix

…y, instead of Restart to Update again

Leo-ask: ask-24cefc7e85

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e1fe72acf384b8bd76b5
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 8d4bdfd: 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=pr31178 -f ref=lane/next-update-install-failed-says-why -f platform=macos -f backend_mode=local

web and size checks: green

Shipped in:

@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
@blacksmith-sh

This comment has been minimized.

The record, the launch read and the staged-build match live on UpdateInstallCheck and
UpdateInstallFailure; UpdaterService grows by no line (2684, as on base).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-e1fe72acf384b8bd76b5

@teamleaderleo teamleaderleo left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (codex gpt-6-astra high): SHIP at 8d4bdfd

Review (codex gpt-6-astra high, router-review): SHIP at 8d4bdfd

Follow-ups:

  1. [P1][follow-up] With updates.notify silent, readyCard needs footerPill or flow.userAsked, so after an automatic relaunch a failed install shows no card, reason or Try Again. Surface failed-install details independently of the silent card preference.

Filed: #31192

@teamleaderleo
teamleaderleo merged commit 6229004 into feat-cmux-next Nov 1, 2026
141 of 145 checks passed
@teamleaderleo
teamleaderleo deleted the lane/next-update-install-failed-says-why branch November 1, 2026 18:13
lawrencecchen added a commit that referenced this pull request Nov 3, 2026
…s sibling suites do (#33093)

theRelaunchRecordsTheInstallAndTheNextLaunchJudgesIt and
anInstallThatEndsWithTheAppRunningLeavesNoRecord call
updaterWillRelaunchApplication(), which reads UpdaterService.frontmost.
Its default is AppKit's answer (UpdateFrontmostApp.current()), which reads
NSApp.isActive. The package-test lane runs each suite in its own process,
and this suite makes no NSApplication, so NSApp is nil and the read traps
(UpdateRelaunchFocus.swift:99, 'Unexpectedly found nil'): the whole suite
crashes. It passed only when an earlier suite in the same process had made
NSApplication.shared.

The seam is there for tests ('AppKit in the app, a fake in tests');
UpdateRelaunchFocusTests and UpdateRelaunchHandoffTests set it. The helper
now sets it too. No product change: the app always has NSApp.

Culprit: 6229004 (#31178) added the suite without the fake.
Red: fleet job 2bf90b6c18ca136f3fb6c15d at feat 11dfe91.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 8d4bdfd6 Deployed Nov 1, 2026 by teamleaderleo via cmux-tui tree preflight #97061
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant