gitbutlerapp/gitbutler · error

Failed to create symlink: {e} - attempting to restore backup

Error message

Failed to create symlink: {e} - attempting to restore backup

What it means

Warning emitted in the final swap phase when unix_fs::symlink(final_target, ~/.local/bin/but) fails after the new app bundle was already moved into place (install_macos.rs:230-237). The installer then rolls back: removes the new bundle, renames GitButler.app.backup back over ~/Applications/GitButler.app, re-creates the old symlink, and bails with 'Failed to create symlink: {e}. Previous installation was restored.' Note the preceding let _ = fs::remove_file(&but_symlink) discards removal errors, so a leftover directory at that path surfaces here as EEXIST/IsADirectory rather than as a clean removal failure.

Source

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

            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!(
            "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 {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Inspect the blocking path: ls -ld ~/.local/bin/but - if it is a directory, remove it (rm -rf ~/.local/bin/but) and re-run the installer
  2. Fix ownership and writability: chown -R $(whoami) ~/.local/bin && chmod u+w ~/.local/bin
  3. Check disk and mount state: df -h ~ (the rollback renames need space and a writable volume)
  4. Confirm the rollback left a working CLI: but --version; the bail message states the previous installation was restored, so nothing is lost
  5. Re-run the installer once the path is clear

Example fix

# before
ls -ld ~/.local/bin/but
drwxr-xr-x  2 me staff  64 but    # directory blocks symlink creation -> EEXIST

# after
rm -rf ~/.local/bin/but
# re-run installer; swap + symlink succeed, no rollback needed
Defensive patterns

Strategy: validation

Validate before calling

# Before installing, make the swap target unobstructed:
[ -d ~/.local/bin/but ] && rm -rf ~/.local/bin/but   # a directory is the classic blocker
[ -e ~/.local/bin/but ] && [ ! -L ~/.local/bin/but ] && mv ~/.local/bin/but /tmp/but.saved
[ -w ~/.local/bin ] && [ -x ~/.local/bin ] || chmod u+rwx ~/.local/bin
df -h ~ | tail -1   # renames need space on the same volume

Prevention

When it happens

Trigger: ~/.local/bin/but is a directory (fs::remove_file cannot remove directories, so symlink creation fails with 'File exists'/'Is a directory'); permission denied on ~/.local/bin; read-only filesystem or full disk; another process recreating the path mid-install.

Common situations: A directory named 'but' created by other tooling in ~/.local/bin; ~/.local/bin owned by root after sudo-assisted setup; sandboxed installers without write access; endpoint-security agents locking the path.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/31bcae047970ad0f. Report an issue: GitHub.