{"record":{"id":"1be8c1db0b5b715d","repo":"gitbutlerapp/gitbutler","slug":"refusing-to-replace-which-points-to-instead-of","errorCode":null,"errorMessage":"Refusing to replace '{}', which points to '{}' instead of '{}'","messagePattern":"Refusing to replace '(.+?)', which points to '(.+?)' instead of '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-action/src/cli.rs","lineNumber":189,"sourceCode":"#[cfg(any(target_os = \"macos\", all(test, unix)))]\nfn validate_cli_source(source: &std::path::Path) -> anyhow::Result<()> {\n    use std::os::unix::fs::PermissionsExt;\n\n    anyhow::ensure!(source.is_absolute(), \"CLI source path must be absolute\");\n    let metadata = std::fs::metadata(source)\n        .with_context(|| format!(\"Cannot access CLI executable at '{}'\", source.display()))?;\n    anyhow::ensure!(\n        metadata.is_file() && metadata.permissions().mode() & 0o111 != 0,\n        \"CLI source '{}' must be an executable file\",\n        source.display()\n    );\n    Ok(())\n}\n\n#[cfg(any(target_os = \"macos\", all(test, unix)))]\nfn verify_cli_link(source: &std::path::Path, destination: &std::path::Path) -> anyhow::Result<()> {\n    let target = std::fs::read_link(destination).context(\"Cannot read installed CLI symlink\")?;\n    anyhow::ensure!(\n        target == source,\n        \"Refusing to replace '{}', which points to '{}' instead of '{}'\",\n        destination.display(),\n        target.display(),\n        source.display()\n    );\n    Ok(())\n}\n\n#[cfg(any(target_os = \"macos\", all(test, unix)))]\nconst INSTALL_CLI_SCRIPT: &str = r#\"\non run argv\n    set sourcePath to quoted form of (item 1 of argv)\n    set targetPath to quoted form of (item 2 of argv)\n    set targetDirectory to quoted form of (item 3 of argv)\n    set symlinkPolicy to quoted form of (item 4 of argv)\n    try\n        do shell script (\"/bin/mkdir -p \" & targetDirectory & \" || exit $?; \" & ¬","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-action/src/cli.rs#L171-L207","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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`."],"exampleFix":"// before: stale symlink blocks install\n$ but install\nError: Refusing to replace '/usr/local/bin/but', which points to '/Applications/GitButler-old.app/...' instead of '/Applications/GitButler.app/...'\n\n// after\n$ sudo rm /usr/local/bin/but\n$ but install  # succeeds","handlingStrategy":"validation","validationCode":"let dest = std::path::Path::new(\"/usr/local/bin/but\");\nif let Ok(target) = std::fs::read_link(dest) {\n    if target != expected_source {\n        eprintln!(\"{} points at {}; remove or move it before installing\", dest.display(), target.display());\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["macos","symlink","cli-installation","safety-guard"],"backgroundTag":"file-already-exists","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}