gitbutlerapp/gitbutler · critical

Failed to create symlink and failed to remove new…

Error message

Failed to create symlink and failed to remove new installation during rollback: {}. Backup at: {}

What it means

After the new .app was moved into place, creating the `but` symlink failed. Rollback tried to remove the newly installed app directory (fs::remove_dir_all(&install_app)) so the backup could be renamed back, and that removal itself failed. The system is left in a dual-state: new (possibly symlink-less) install plus a backup, so the error message reports the backup path.

Solutions

  1. Quit any GitButler processes (`pkill -f gitbutler-tauri`) and retry the install
  2. Manually complete the rollback: `rm -rf <new GitButler.app>` then `mv <backup path> <install path>` using the backup path in the message
  3. Check ~/.local/bin/but is not a directory (`file ~/.local/bin/but`); remove it so a symlink can replace it
  4. Investigate why remove_dir_all failed (immutable flags, permissions, AV lock) with `ls -laO` on the app bundle

Example fix

// before: manual recovery guesswork
// after: scripted recovery from the message
rm -rf ~/Applications/GitButler.app
mv ~/.local/share/gitbutler/GitButler.app.backup ~/Applications/GitButler.app
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-checks: no processes using the app, symlink path clear
let but_path = home.join(".local/bin/but");
if let Ok(m) = fs::symlink_metadata(&but_path) {
    if !m.file_type().is_symlink() {
        bail!("{} is not a symlink - symlink creation will fail", but_path.display());
    }
}
let _ = Command::new("pgrep").args(["-f", "gitbutler-tauri"]).output()?;

Try / catch

if let Err(e) = installer::download_and_install_app(...) {
    if e.to_string().contains("failed to remove new installation during rollback") {
        // dual state: finish rollback manually using the backup path in the message
        eprintln!("manually restore from the backup path shown in: {e}");
    }
}

Prevention

When it happens

Trigger: install_app when unix_fs::symlink(final_target, but_symlink) fails AND install_app_backup exists AND fs::remove_dir_all(&install_app) fails during rollback - typically because files in the new app bundle are in use or permission-protected.

Common situations: A GitButler process was launched from the new app between the rename and the symlink step; permission-controlled directories inside the new bundle (added malware protections, AV scan); the `but` path in ~/.local/bin is itself a directory so symlink(2) fails with EEXIST while rm -rf of the app also hits a busy file.

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/36dc286f24f5bb57. Report an issue: GitHub.

Appendix: source

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

        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!(
            "Failed to create symlink: {e} - attempting to restore backup"
        ));
        if install_app_backup.exists() {
            if let Err(remove_err) = fs::remove_dir_all(&install_app) {
                bail!(
                    "Failed to create symlink and failed to remove new installation during rollback: {}. Backup at: {}",
                    remove_err,
                    install_app_backup.display()
                );
            }
            fs::rename(&install_app_backup, &install_app)?;

            let restored_target = install_app.join("Contents/MacOS/gitbutler-tauri");
            let _ = fs::remove_file(&but_symlink);
            let _ = unix_fs::symlink(&restored_target, &but_symlink);

            bail!("Failed to create symlink: {e}. Previous installation was restored.");
        } else {
            bail!("Failed to create symlink: {e}. No backup available to restore.");
        }
    }

    if !validate_installed_binary(&but_symlink) {

View on GitHub (pinned to 58e5313667)