Skip to content

fix(installer): never brick the install when a self-update swap fails - #38402

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/self-update-no-brick-on-failed-swap
Jun 3, 2026
Merged

fix(installer): never brick the install when a self-update swap fails#38402
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/self-update-no-brick-on-failed-swap

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #38296. The macOS self-update bundle swap (install_macos_app_update) has a failure path that can leave the user with no app installed at all.

The bug

In the swap sequence:

let moved_old = if target_app.exists() {
    match tokio::fs::rename(target_app, &old).await {
        Ok(()) => true,
        Err(_) => {
            remove_dir_if_exists(target_app).await;   // ← deletes the running app
            false
        }
    }
} else { false };
if let Err(err) = tokio::fs::rename(&tmp, target_app).await {
    if moved_old {                                     // ← false here ⇒ no rollback
        let _ = tokio::fs::rename(&old, target_app).await;
    }
    return Err(...);
}

If moving the existing /Applications/Hermes.app aside fails, the code deletes target_app and sets moved_old = false. If the subsequent move of the freshly built bundle into place then also fails, the rollback is gated on moved_old (now false) and is skipped — leaving the target deleted with no replacement. A failed update bricks the install.

The fix

Extract the swap into swap_in_new_bundle() with a strict invariant: on any failure path, target is left pointing at a working bundle (the original — rolled back or untouched) and is never deleted with no replacement. Also clean up the staged .hermes-update-new copy on the failure paths instead of orphaning it.

Tests

Adds three #[tokio::test] cases in update.rs:

  • swap_installs_new_bundle_and_cleans_up — happy path
  • swap_rolls_back_when_install_step_fails — move-aside ok, install fails ⇒ original restored
  • swap_failure_never_leaves_target_missingboth moves fail ⇒ original survives

The catastrophic-path test was verified to FAIL against the pre-fix code (original app must NOT be deleted on failure) and pass against the fix, so it's a real regression guard.

cargo test  →  18 passed; 0 failed

Scope: apps/bootstrap-installer/src-tauri/src/update.rs only. macOS-gated path; no behavior change on the success path.

The macOS self-update bundle swap (install_macos_app_update, added in
NousResearch#38296) could leave the user with NO app installed. If moving the
existing /Applications/Hermes.app aside failed, the code deleted the
running app outright and set moved_old=false; if the subsequent move of
the freshly built bundle into place then also failed, the rollback was
gated on moved_old (now false) and skipped — leaving the target deleted
with no replacement.

Extract the swap into swap_in_new_bundle() with a strict invariant: on
ANY failure path the target is left pointing at a working bundle (either
the original, rolled back, or untouched) and is never deleted with no
replacement. Also clean up the staged .hermes-update-new copy on the
failure paths instead of orphaning it.

Add unit tests covering the happy path, the rollback-on-install-failure
path, and the catastrophic both-moves-fail path. The catastrophic-path
test was verified to FAIL against the old code ("original app must NOT
be deleted on failure") and pass against the fix.
@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists labels Jun 3, 2026
@kshitijk4poor
kshitijk4poor merged commit d3b1e43 into NousResearch:main Jun 3, 2026
20 checks passed
@kshitijk4poor
kshitijk4poor deleted the fix/self-update-no-brick-on-failed-swap branch August 5, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants