fix: make macOS desktop self-update swap+relaunch fail-fast and recoverable - #43265
fix: make macOS desktop self-update swap+relaunch fail-fast and recoverable#43265OmarB97 wants to merge 1 commit into
Conversation
|
Maintainer-ready after refresh. I merged current Verification:
MeshBoard merge dry-run passes with green checks and a fresh base. I attempted the actual merge through |
…erable The macOS in-app updater's detached swap script ran with plain 'set -u' and a success-gated ditto: when ditto or the destination move failed, the script fell through silently -- the app had already quit, the old bundle stayed (or was left moved aside), and 'open "$DST"' either relaunched the stale build or nothing at all. This matches the reported "Update now does not actually update" behavior. Make the swap fail-fast and recoverable: - set -euo pipefail so unexpected failures stop the script instead of compounding. - Detect ditto and destination-replace failures explicitly; on either, fall back to opening the freshly rebuilt bundle directly so the user is never left with a dead quit. - Clean up the .hermes-update-old copy via an EXIT trap so it is removed on every exit path. Recovered from the pre-force-push head of desktop-mac-swap-fix (NousResearch#38410, which lost this work to a force-push and was closed). The sqlite busy_timeout half of that PR is intentionally not revived: the BEGIN IMMEDIATE + jitter-retry redesign (NousResearch#3385) deliberately keeps the connection busy handler short, and a 5s busy_timeout would override that design. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4648a5c to
a608117
Compare
|
Refreshed onto upstream/main (rebase). This branch's prior head ( |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for reviving the macOS recovery path. The underlying defect remains on current main: apps/desktop/electron/main.ts:3028 does not check the new-bundle move before deleting the old bundle and opening the destination.
Problems
apps/desktop/electron/main.ts:3016addsset -euo pipefail, but the retainedmv "$DST" "$DST.hermes-update-old" || rm -rf "$DST"path has no fallback if both commands fail. The script exits beforeopen "$SRC", after the app has already quit.apps/desktop/electron/main.ts:3024cleans only.hermes-update-old. A faileddittoor failed destination replacement can leave.hermes-update-new, contrary to the stated staging-cleanup invariant.- No repository test covers this macOS script;
apps/desktop/AGENTS.md:179calls for exercising the actual failure rungs.
Suggested changes
- Add an explicit recovery branch for failure to displace the destination, and clean both staging paths on every exit.
- Commit the rendered-script simulations as an automated test covering success plus each failure branch.
Automated hermes-sweeper review.
| // then atomically replace the running .app, clear quarantine, and reopen it. | ||
| // If the swap fails, fall back to launching the freshly rebuilt bundle | ||
| // directly so we do not leave the user with a dead quit. | ||
| const swapScript = `#!/bin/bash |
There was a problem hiding this comment.
set -e makes the existing mv "$DST" "$DST.hermes-update-old" || rm -rf "$DST" sequence fatal if both commands fail, before either explicit open "$SRC" fallback. Handle that displacement failure explicitly so the recovery invariant holds.
| for _ in $(seq 1 240); do | ||
| kill -0 "$APP_PID" 2>/dev/null || break | ||
| sleep 0.5 | ||
| done |
There was a problem hiding this comment.
This trap removes only .hermes-update-old; a failed ditto or destination replacement can leave .hermes-update-new. Clean both staging paths so the stated no-orphaned-staging invariant is true.
GottZ
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Summary
Three PRs address or reference the macOS desktop self-update failure. #35607 introduced the POSIX rebuild/swap/relaunch path, #38410’s surviving diff only ad-hoc re-signs the copied installer and does not implement its described recovery changes, and #43265 hardens the swap script but still leaves documented failure paths uncovered.
Related pull requests
- #35607 [merged]
related— (+340/-14) — merged reference implementation: introduced the macOS/Linux in-app update flow, including rebuilding the desktop bundle and swapping/relaunching it, along with installer-path and setup fixes. It remains relevant because its detached swap script is the implementation in which the reported dead-quit failure originates. - #38410 [closed]
related— (+3/-0) — closed, not the promised recovery fix: the surviving diff only invokes ad-hoc codesigning after copying the bootstrap installer; it contains none of the described swap fail-fast or SQLite changes. It remains relevant as provenance for the recovery attempt, but was closed because its sole commit was superseded by #38446 and the intended updater work was lost. - #43265
related— (+17/-7) — keep open: revives the intended swap recovery by adding fail-fast shell behavior and opening the rebuilt bundle whendittoor final replacement fails. This agrees with the contributor keep_open review on #43265: the diff still exits without recovery if both destination displacement commands fail, cleans.hermes-update-oldbut not.hermes-update-new, and adds no automated coverage for the required failure rungs.
Suggested consolidation
Keep #43265 open rather than merging it yet; address the contributor review by adding explicit recovery when the existing destination cannot be displaced, cleaning both staging paths on every exit, and committing rendered-script tests for success and each failure branch. Once those blockers are resolved and verified, merge #43265 as the focused fix; #35607 remains the merged reference implementation, and closed #38410 is not a diff-level duplicate requiring further action.
Cross-PR triage: Reviewed 3 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 21 kB of PR diffs, 7 kB of issue/PR text, 7 kB of discussion (8 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.
Why
Revives #38410, which was closed after an old force-push left its branch without the changes the description promised. The original commit was recovered from the fork's pre-force-push branch head and rebased onto current main.
The macOS in-app updater can quit
Hermes.appand never reinstall/reopen — matching the reported "Update now does not actually update" behavior. The detached swap script gated onlydittoon success and fell through silently when the bundle swap failed, leaving the user on the old build (or with no app at all) after a dead quit.What changed
apps/desktop/electron/main.cjs: theapplyUpdatesPosixInAppswap script now runsset -euo pipefail, detectsdittoand destination-replace failures immediately, cleans up the.hermes-update-oldstaging copy via an EXIT trap, and on any swap failure falls back to opening the freshly rebuilt bundle directly instead of leaving the user with a dead quit.Unlike #38410, this does not touch
hermes_state.py: thePRAGMA busy_timeout = 5000half of that PR is superseded by the BEGIN IMMEDIATE + jitter-retry redesign (#3385), which deliberately keeps the SQLite busy handler short and handles contention with application-level retries.How to review
Review the
swapScripttemplate inapplyUpdatesPosixInApp(apps/desktop/electron/main.cjs, ~line 1815). The invariant: on ANY swap failure the user ends up with a working app launched (the rebuilt bundle via theopen "$SRC"fallback) and no orphaned.hermes-update-*staging copies.Evidence
Functional simulation of the rendered swap script with
/usr/bin/openand/usr/bin/xattrstubbed:ditto-failure path (source missing): destination untouched (old bundle intact),[updates] ditto failedlogged to stderr, fallback open targets the rebuilt source bundle, no leftovers, rc=0.Verification
node --check apps/desktop/electron/main.cjsbash -non the rendered swap scriptdittofail) opens the rebuiltrelease/mac-arm64/Hermes.appinstead of dead-quitting, and a normal update still replaces/Applications/Hermes.app.Risks & gaps
rm -rfof it also fails,set -eaborts with the original app intact but no relaunch (the user reopens manually) — strictly better than the prior silent fall-through.open "$SRC"fallback launches the rebuilt bundle from the build tree rather than/Applications; this avoids a no-app state but may not match the user's preferred installed location (same trade-off as reviewed in fix: make macOS desktop self-update swap+relaunch fail-fast and recoverable #38410).