gitbutlerapp/gitbutler · error
Refusing to replace ' ', which points to ' ' instead of
Error message
Refusing to replace '{}', which points to '{}' instead of '{}' What it means
During GitButler CLI installation on macOS, verify_cli_link reads the existing symlink at the destination (e.g. /usr/local/bin/but) and refuses to proceed when it points somewhere other than the expected CLI executable. This is a safety guard so the installer never silently clobbers a symlink the user (or another tool) installed to a different binary. The message includes the destination, the unexpected current target, and the expected target.
Solutions
- Remove the stale symlink manually: `sudo rm /usr/local/bin/but`, then retry the install.
- Check where it currently points with `ls -l /usr/local/bin/but`; if it points to an existing binary you still want, move it aside instead of deleting it.
- If it is a dangling link to an old app bundle, reinstalling via the GitButler app's CLI-install flow (or auto_fix_broken_but_cli_symlink) replaces it.
- If the symlink is correct but paths differ only by form (relative vs absolute), recreate it with an absolute target: `sudo ln -sf <absolute-path-to-but> /usr/local/bin/but`.
Example fix
// before: stale symlink blocks install $ but install Error: Refusing to replace '/usr/local/bin/but', which points to '/Applications/GitButler-old.app/...' instead of '/Applications/GitButler.app/...' // after $ sudo rm /usr/local/bin/but $ but install # succeeds
Defensive patterns
Strategy: validation
Validate before calling
let dest = std::path::Path::new("/usr/local/bin/but");
if let Ok(target) = std::fs::read_link(dest) {
if target != expected_source {
eprintln!("{} points at {}; remove or move it before installing", dest.display(), target.display());
}
} Prevention
- Check `ls -l <dest>` for stale symlinks after uninstalling or moving the app before reinstalling the CLI.
- Recreate symlinks with absolute targets so path-form mismatches never trip verification.
- Prefer the app's own install/repair flow (auto_fix_broken_but_cli_symlink) over manual linking.
When it happens
Trigger: Calling install_cli_link or install_cli_link_escalated when the destination path already contains a symlink whose read_link() target differs from the resolved `but` CLI source path (e.g. a symlink to a previously installed version in an uninstalled app bundle, or a user-made symlink to their own binary).
Common situations: A stale symlink left behind after uninstalling or moving the GitButler app (a dangling link to an old .app bundle path); the user previously linked their own `but`/other tool at the same path; a version upgrade changed the bundled CLI source location so the old link no longer matches.
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
- A 'but' binary already exists at
- CLI installation failed
- CliInstallCancelled
- Failed to create symlink
- Found existing 'but' symlink pointing to
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/1be8c1db0b5b715d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-action/src/cli.rs:189
#[cfg(any(target_os = "macos", all(test, unix)))]
fn validate_cli_source(source: &std::path::Path) -> anyhow::Result<()> {
use std::os::unix::fs::PermissionsExt;
anyhow::ensure!(source.is_absolute(), "CLI source path must be absolute");
let metadata = std::fs::metadata(source)
.with_context(|| format!("Cannot access CLI executable at '{}'", source.display()))?;
anyhow::ensure!(
metadata.is_file() && metadata.permissions().mode() & 0o111 != 0,
"CLI source '{}' must be an executable file",
source.display()
);
Ok(())
}
#[cfg(any(target_os = "macos", all(test, unix)))]
fn verify_cli_link(source: &std::path::Path, destination: &std::path::Path) -> anyhow::Result<()> {
let target = std::fs::read_link(destination).context("Cannot read installed CLI symlink")?;
anyhow::ensure!(
target == source,
"Refusing to replace '{}', which points to '{}' instead of '{}'",
destination.display(),
target.display(),
source.display()
);
Ok(())
}
#[cfg(any(target_os = "macos", all(test, unix)))]
const INSTALL_CLI_SCRIPT: &str = r#"
on run argv
set sourcePath to quoted form of (item 1 of argv)
set targetPath to quoted form of (item 2 of argv)
set targetDirectory to quoted form of (item 3 of argv)
set symlinkPolicy to quoted form of (item 4 of argv)
try
do shell script ("/bin/mkdir -p " & targetDirectory & " || exit $?; " & ¬View on GitHub (pinned to 58e5313667)