gitbutlerapp/gitbutler · warning · anyhow::Error
Refusing to install symlink onto existing non-symlink at '{U
Error message
Refusing to install symlink onto existing non-symlink at '{UNIX_LINK_PATH}' What it means
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").
Source
Thrown at crates/but-action/src/cli.rs:31
const UNIX_LINK_PATH: &str = "/usr/local/bin/but";
pub enum InstallMode {
AllowPrivilegeElevation,
CurrentUserOnly,
}
pub fn do_install_cli(mode: InstallMode) -> anyhow::Result<()> {
let cli_path = get_cli_path()?;
#[cfg(windows)]
{
return install_cli_windows(cli_path);
}
match std::fs::symlink_metadata(UNIX_LINK_PATH) {
Ok(md) => {
if !md.is_symlink() {
bail!(
"Refusing to install symlink onto existing non-symlink at '{UNIX_LINK_PATH}'"
);
}
let current_link = std::fs::read_link(UNIX_LINK_PATH)
.context(format!("error reading existing link: {UNIX_LINK_PATH}"))?;
if current_link == cli_path {
return Ok(());
}
ensure_cli_path_exists_prior_to_link(&cli_path)?;
#[cfg(not(windows))]
if std::fs::remove_file(UNIX_LINK_PATH)
.and_then(|_| std::os::unix::fs::symlink(&cli_path, UNIX_LINK_PATH))
.is_ok()
{
return Ok(());
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {View on GitHub (pinned to caf1f223d3)
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
Example fix
# before: /usr/local/bin/but is a regular file -> install refuses $ ls -l /usr/local/bin/but -rwxr-xr-x 1 root staff 41324672 Jan 1 10:00 /usr/local/bin/but # after: remove the copy and re-run install (from the app or CLI) $ rm /usr/local/bin/but $ # retry install -> creates symlink to the built binary $ ls -l /usr/local/bin/but lrwxr-xr-x 1 root staff 9 Aug 20 11:00 /usr/local/bin/but -> .../target/tauri/but
Defensive patterns
Strategy: validation
Validate before calling
// Check the link path before invoking install
let md = std::fs::symlink_metadata("/usr/local/bin/but");
if let Ok(md) = &md {
if md.exists() && !md.is_symlink() {
// resolve manually: remove/rename the foreign file, then install
}
} Type guard
fn link_path_is_free_or_symlink() -> bool {
match std::fs::symlink_metadata("/usr/local/bin/but") {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,
Ok(md) => md.is_symlink(),
Ok(_) => false,
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Automatic CLI installation is not supported on Windows. To
- A 'but' binary already exists at {} (not a symlink)
- Run `CARGO_TARGET_DIR=$PWD/target/tauri cargo build -p but`
- Invalid response: ${JSON.stringify(result)}
- When using OpenAI in a bring your own key configuration, you
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/b14eb270e86a97a5.
Report an issue: GitHub.