gitbutlerapp/gitbutler · critical

Failed to install new version

Error message

Failed to install new version: {}. Also failed to restore backup: {}. Your app may be at {}

What it means

In the macOS installer's critical failure path, moving the new .app into place failed (`e`) and restoring the backed-up previous installation also failed (`restore_err`). The error chains both failures plus the backup location so the user can manually recover their previous installation.

Solutions

  1. Quit GitButler completely (check Activity Monitor for lingering processes) and re-run the installer.
  2. Manually restore: move the backup at the path named in the error message back to the app's install location (e.g. /Applications/GitButler.app).
  3. Ensure you have write permission to the install directory (install via sudo, or use a user Applications folder).
  4. Re-run the installer afterwards to complete the upgrade once the destination is writable.

Example fix

// before
but upgrade   # both install and restore failed
// after
osascript -e 'quit app "GitButler"'   # ensure app not running
sudo mv "$BACKUP_PATH" /Applications/GitButler.app   # path from error message
but upgrade
Defensive patterns

Strategy: fallback

Validate before calling

// ensure destination is on the same volume as staging and not currently in use
let running = std::process::Command::new("pgrep")
    .args(["-x", "GitButler"]).output()
    .map(|o| !o.stdout.is_empty()).unwrap_or(false);
if running { anyhow::bail!("GitButler is running; quit it before upgrading"); }
// check writability of the install parent (e.g. /Applications)
let probe = install_dir.join(".probe");
std::fs::write(&probe, b"").context("install directory not writable")?;
let _ = std::fs::remove_file(&probe);

Try / catch

match install_app(&config) {
    Err(e) if e.to_string().contains("Your app may be at") => {
        let backup = extract_backup_path(&e.to_string());
        eprintln!("restoring manually from {backup:?}");
        std::fs::rename(backup, "/Applications/GitButler.app")?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: The rename of the staged new .app over the existing install fails (permissions, destination locked because the app is running, cross-volume rename) and the subsequent rename of the backup back to the install path also fails (same causes, or backup already moved/deleted).

Common situations: The GitButler app is running and macOS holds a lock on the bundle; installing from a context without write permission to /Applications; the target is on a different volume than the staging dir (APFS External/FAT USB); Finder/antivirus interfering mid-swap.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

    // Backup existing installation if it exists
    let had_backup = install_app.exists();
    if had_backup && let Err(e) = fs::rename(&install_app, &install_app_backup) {
        // Failed to backup - clean up artifacts before failing
        cleanup_artifacts();
        return Err(e.into());
    }

    // 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);

View on GitHub (pinned to 58e5313667)