{"record":{"id":"b14eb270e86a97a5","repo":"gitbutlerapp/gitbutler","slug":"refusing-to-install-symlink-onto-existing-non-syml","errorCode":null,"errorMessage":"Refusing to install symlink onto existing non-symlink at '{UNIX_LINK_PATH}'","messagePattern":"Refusing to install symlink onto existing non-symlink at '(.+?)'","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/but-action/src/cli.rs","lineNumber":31,"sourceCode":"\nconst UNIX_LINK_PATH: &str = \"/usr/local/bin/but\";\n\npub enum InstallMode {\n    AllowPrivilegeElevation,\n    CurrentUserOnly,\n}\n\npub fn do_install_cli(mode: InstallMode) -> anyhow::Result<()> {\n    let cli_path = get_cli_path()?;\n    #[cfg(windows)]\n    {\n        return install_cli_windows(cli_path);\n    }\n\n    match std::fs::symlink_metadata(UNIX_LINK_PATH) {\n        Ok(md) => {\n            if !md.is_symlink() {\n                bail!(\n                    \"Refusing to install symlink onto existing non-symlink at '{UNIX_LINK_PATH}'\"\n                );\n            }\n            let current_link = std::fs::read_link(UNIX_LINK_PATH)\n                .context(format!(\"error reading existing link: {UNIX_LINK_PATH}\"))?;\n            if current_link == cli_path {\n                return Ok(());\n            }\n            ensure_cli_path_exists_prior_to_link(&cli_path)?;\n            #[cfg(not(windows))]\n            if std::fs::remove_file(UNIX_LINK_PATH)\n                .and_then(|_| std::os::unix::fs::symlink(&cli_path, UNIX_LINK_PATH))\n                .is_ok()\n            {\n                return Ok(());\n            }\n        }\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-action/src/cli.rs#L13-L49","documentation":"but-action's CLI installer manages /usr/local/bin/but strictly as a symlink to the built binary. If something already exists at that path and is not a symlink (a copied binary, a Homebrew-owned file), it refuses rather than clobber a file it does not own (crates/but-action/src/cli.rs:31, UNIX_LINK_PATH = \"/usr/local/bin/but\").","triggerScenarios":"Running the desktop app's 'install CLI' action (do_install_cli) when /usr/local/bin/but already exists as a regular file - e.g. an older GitButler release copied the binary there, or another distribution method placed a real file at that path.","commonSituations":"Upgrading from an old GitButler version that copied instead of symlinked; /usr/local/bin managed by Homebrew on macOS; a manually copied but binary from a release tarball.","solutions":["Inspect what is there: `ls -l /usr/local/bin/but` and `file /usr/local/bin/but`","If it is an old copy of the but CLI you no longer need, remove it (`rm /usr/local/bin/but`) or rename it aside, then retry the install","If another package owns the path, keep that install and skip the symlink install, or choose a different link location on your PATH"],"exampleFix":"# before: /usr/local/bin/but is a regular file -> install refuses\n$ ls -l /usr/local/bin/but\n-rwxr-xr-x 1 root staff 41324672 Jan 1 10:00 /usr/local/bin/but\n\n# after: remove the copy and re-run install (from the app or CLI)\n$ rm /usr/local/bin/but\n$ # retry install -> creates symlink to the built binary\n$ ls -l /usr/local/bin/but\nlrwxr-xr-x 1 root staff 9 Aug 20 11:00 /usr/local/bin/but -> .../target/tauri/but","handlingStrategy":"validation","validationCode":"// Check the link path before invoking install\nlet md = std::fs::symlink_metadata(\"/usr/local/bin/but\");\nif let Ok(md) = &md {\n    if md.exists() && !md.is_symlink() {\n        // resolve manually: remove/rename the foreign file, then install\n    }\n}","typeGuard":"fn link_path_is_free_or_symlink() -> bool {\n    match std::fs::symlink_metadata(\"/usr/local/bin/but\") {\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,\n        Ok(md) => md.is_symlink(),\n        Ok(_) => false,\n    }\n}","tryCatchPattern":null,"preventionTips":["Prefer one installation mechanism (symlink or copy) per machine; do not mix them","Before running the app's install action, check `ls -l /usr/local/bin/but` for a non-symlink file","If another package owns the path, install the CLI elsewhere on PATH instead of forcing the link"],"tags":["rust","cli","symlink","install","path-conflict","but-action"],"backgroundTag":"symlink-target-conflict","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}