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
- Quit GitButler completely (check Activity Monitor for lingering processes) and re-run the installer.
- 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).
- Ensure you have write permission to the install directory (install via sudo, or use a user Applications folder).
- 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
- Always quit the app before upgrading so macOS doesn't hold the bundle open.
- Stage the new .app on the same volume as the destination so renames are atomic (same APFS volume).
- Run the updater with rights to write the install directory (admin or user Applications dir).
- Note the backup path from this error immediately — it is your recovery point for a manual restore.
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
- Failed to create symlink and failed to remove new…
- Installation failed and backup restoration also failed -…
- Moving it to to preserve your existing file
- New installation verification failed - 'but' binary cannot…
- A 'but' binary already exists at
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)