gitbutlerapp/gitbutler · error

Failed to install new version

Error message

Failed to install new version: {e}. Previous installation restored successfully.

What it means

During a GitButler CLI (but) macOS self-install, the atomic swap step (fs::rename of the freshly downloaded .app into the install location) failed. The installer attempted to roll back by renaming the previous installation from its backup path, and that rollback succeeded, so the user's old app is intact. The bail() carries both the underlying rename error and the reassurance that the restore worked.

Solutions

  1. Quit all GitButler/but processes (Activity Monitor or `pkill -f gitbutler`) and retry the install
  2. Check free disk space and that the install volume is writable (`df -h ~`, `ls -ld ~/Applications`)
  3. Run the installer again; the previous installation was restored, so a clean retry is safe
  4. If rename keeps failing across mount points, ensure the download temp dir and install dir are on the same volume

Example fix

// before (user script ignoring the failure)
let res = installer::download_and_install_app(...);
// after
match res {
    Err(e) if e.to_string().contains("restored successfully") => {
        eprintln!("Install failed but old app intact; kill running GitButler and retry: {e}");
    }
    other => other?,
}
Defensive patterns

Strategy: retry

Validate before calling

// Before install: ensure no running GitButler and target volume is writable
use std::process::Command;
let running = Command::new("pgrep")
    .args(["-f", "gitbutler"])
    .output()
    .map(|o| o.status.success())
    .unwrap_or(false);
assert!(!running, "GitButler is running; quit it before installing");
assert!(fs::metadata(install_parent)?.permissions().readonly() == false, "install dir not writable");

Try / catch

match installer::download_and_install_app(...) {
    Err(e) if e.to_string().contains("restored successfully") => {
        // old install intact: safe to kill processes and retry
        eprintln!("retry install after closing GitButler: {e}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: install_app (via download_and_install_app) when: had_backup was true (an existing GitButler.app existed), fs::rename(&install_app_new, &install_app) failed (e.g. destination locked, cross-device rename, target on read-only volume), and the subsequent fs::rename(&install_app_backup, &install_app) succeeded.

Common situations: Another GitButler process is running and holds the app bundle open; the install volume (~/.local or ~/Applications on a different mount) is read-only or full; a directory exists at the destination that blocks the rename; disk permission issues after a macOS update.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/591fc85aa32dbe13. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-installer/src/install_macos.rs:217

    }

    // Move new installation into place with rollback on failure
    if let Err(e) = fs::rename(&install_app_new, &install_app) {
        // Clean up stale artifacts before handling the error
        cleanup_artifacts();

        // Critical failure - restore backup if we created one
        if had_backup && install_app_backup.exists() {
            warn("Failed to move new installation into place - restoring backup");
            if let Err(restore_err) = fs::rename(&install_app_backup, &install_app) {
                bail!(
                    "Failed to install new version: {}. Also failed to restore backup: {}. Your app may be at {}",
                    e,
                    restore_err,
                    install_app_backup.display()
                );
            }
            bail!(
                "Failed to install new version: {e}. Previous installation restored successfully."
            );
        }
        // No backup to restore, just fail
        bail!("Failed to move new installation into place: {e}");
    }

    // Update the symlink to point to the new installation
    let final_target = install_app.join("Contents/MacOS/gitbutler-tauri");
    let _ = fs::remove_file(&but_symlink);

    // Try to create symlink and verify - if either fails, rollback
    let symlink_result = unix_fs::symlink(&final_target, &but_symlink);
    let _ = fs::remove_file(&but_new);

    if let Err(e) = symlink_result {
        // Symlink creation failed - rollback to backup
        warn(&format!(

View on GitHub (pinned to 58e5313667)