gitbutlerapp/gitbutler · warning

Moving it to {} to preserve your existing file

Error message

Moving it to {} to preserve your existing file

What it means

Warning from the GitButler macOS installer's CLI setup step (install_macos.rs). When ~/.local/bin/but exists as a regular file rather than a symlink, the installer renames it to ~/.local/bin/but.backup.<unix-seconds> before creating its own symlink, so your existing binary is never destroyed. The subsequent fs::rename uses '?', so if the rename itself fails the install aborts with an io::Error. After the swap, 'but' points to ~/Applications/GitButler.app/Contents/MacOS/gitbutler-tauri and your old binary lives at the printed backup path.

Source

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

    fs::create_dir_all(&bin_dir)?;

    // Check for existing 'but' and detect channel switching
    let but_symlink = bin_dir.join("but");
    let but_new = bin_dir.join("but.new");

    if but_symlink.exists() && !but_symlink.is_symlink() {
        // 'but' exists but is not a symlink
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        let timestamp = format!("{now}");
        let but_backup = bin_dir.join(format!("but.backup.{timestamp}"));
        warn(&format!(
            "A 'but' binary already exists at {} (not a symlink)",
            but_symlink.display()
        ));
        warn(&format!(
            "Moving it to {} to preserve your existing file",
            but_backup.display()
        ));
        fs::rename(&but_symlink, &but_backup)?;
        info(&format!(
            "Your original 'but' has been saved to: {}",
            but_backup.display()
        ));
    } else if but_symlink.is_symlink() {
        let existing_target = fs::read_link(&but_symlink)?;
        let existing_target_str = existing_target.to_string_lossy();

        // Detect channel switching
        let previous_channel = if existing_target_str.contains("Nightly") {
            Some(Channel::Nightly)
        } else if existing_target_str.contains("/GitButler.app/") {
            Some(Channel::Release)
        } else {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Let the installer finish - your file is preserved at the printed but.backup.<timestamp> path; verify with ls -la ~/.local/bin/but*
  2. If you still need the old binary on PATH, rename the backup: mv ~/.local/bin/but.backup.<timestamp> ~/.local/bin/but-custom
  3. Preempt on future installs: mv ~/.local/bin/but ~/bin/but-custom before running the installer
  4. If the install aborted on the rename, fix ownership/permissions of ~/.local/bin (ls -ld ~/.local/bin; chown -R $(whoami) ~/.local/bin) and re-run

Example fix

# before
ls -la ~/.local/bin/but
-rwxr-xr-x  1 me  staff  24563200  but        # real binary, will be moved

# after installer runs (auto-backup)
but -> ~/Applications/GitButler.app/Contents/MacOS/gitbutler-tauri
but.backup.1755689000                                        # your original, kept
# keep it on PATH under a new name if needed:
mv ~/.local/bin/but.backup.1755689000 ~/.local/bin/but-custom
Defensive patterns

Strategy: validation

Validate before calling

# Before running the GitButler macOS installer:
if [ -f ~/.local/bin/but ] && [ ! -L ~/.local/bin/but ]; then
  echo "regular file at ~/.local/bin/but - will be moved to but.backup.<ts>"
  mv ~/.local/bin/but ~/bin/but-custom  # or wherever you want it
fi

Prevention

When it happens

Trigger: Running the macOS app installer while ~/.local/bin/but is a real file: a cargo-installed but CLI, a homebrew-placed binary, a manually compiled/copied executable, or a dotfile manager that writes actual binaries into ~/.local/bin. The branch is taken when but_symlink.exists() && !but_symlink.is_symlink() (install_macos.rs:118).

Common situations: Developer previously built/installed the but CLI from source; another tool also named 'but' on PATH; leftover binary from an earlier manual install; ~/.local/bin shared across machines via sync.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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