gitbutlerapp/gitbutler · error

Would probably need to run "ln -sf '{}' '{UNIX_LINK_PATH}'"{

Error message

Would probably need to run "ln -sf '{}' '{UNIX_LINK_PATH}'"{privilege}

What it means

do_install_cli returns this error when it could not create the /usr/local/bin/but symlink itself and cannot (Linux, or InstallMode::CurrentUserOnly on macOS) or is not allowed to elevate via osascript. It is advisory: the message prints the exact `ln -sf` command the user would need to run manually, optionally suffixed with 'with root permissions'.

Source

Thrown at crates/but-action/src/cli.rs:100

            // dedicated Code so the frontend can react based on the code
            // rather than matching on an English message.
            Err(
                anyhow!("osascript exited with status 1").context(ErrorContext::new_static(
                    Code::CliInstallCancelled,
                    "CLI install cancelled",
                )),
            )
        } else {
            Err(anyhow!(
                "osascript exited with status {}",
                status
                    .code()
                    .map(|c| c.to_string())
                    .unwrap_or_else(|| "unknown".into())
            ))
        }
    } else {
        Err(anyhow!(
            "Would probably need to run \"ln -sf '{}' '{UNIX_LINK_PATH}'\"{privilege}",
            cli_path.display(),
            privilege = if can_elevate_privileges {
                " with root permissions"
            } else {
                ""
            }
        ))
    }
}

fn ensure_cli_path_exists_prior_to_link(cli_path: &std::path::Path) -> anyhow::Result<()> {
    if cli_path.exists() {
        return Ok(());
    }
    bail!("Run `CARGO_TARGET_DIR=$PWD/target/tauri cargo build -p but` to build the `but` binary")
}

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Run the exact command from the error text (it contains the real paths): sudo ln -sf '<cli_path>' /usr/local/bin/but.
  2. Prefer a user-writable directory already in PATH: mkdir -p ~/.local/bin && ln -sf '<cli_path>' ~/.local/bin/but.
  3. Verify the '<cli_path>' binary exists first — the installer separately bails with a cargo build hint if it does not.
  4. If /usr/local/bin/but exists as a regular file (not a symlink), remove it: the installer refuses to overwrite non-symlinks.

Example fix

sudo ln -sf "$PWD/target/tauri/debug/but" /usr/local/bin/but
# or user-local:
mkdir -p ~/.local/bin && ln -sf "$PWD/target/tauri/debug/but" ~/.local/bin/but && hash -r
but --version
Defensive patterns

Strategy: fallback

Validate before calling

use std::path::Path;

fn pick_writable_link_dir() -> std::path::PathBuf {
    let usr_local = Path::new("/usr/local/bin");
    let probe = usr_local.join(".but-probe");
    if usr_local.is_dir() && std::fs::write(&probe, b"").is_ok() {
        let _ = std::fs::remove_file(&probe);
        return usr_local.to_path_buf();
    }
    std::env::var_os("HOME").map(|h| Path::new(&h).join(".local/bin")).unwrap()
}

Try / catch

match do_install_cli(InstallMode::CurrentUserOnly) {
    Ok(()) => Ok(()),
    Err(err) => {
        // The error text IS the manual instruction: show it as copy-pasteable steps.
        show_manual_install_hint(&err.to_string());
        Ok(())
    }
}

Prevention

When it happens

Trigger: do_install_cli on Linux, or on macOS with InstallMode::CurrentUserOnly, after std::os::unix::fs::symlink to /usr/local/bin/but failed (directory not user-writable, or the earlier remove+symlink retry failed) — the final else branch emits the manual `ln -sf` hint.

Common situations: Linux systems where /usr/local/bin is root-owned; CI containers running the app as non-root; macOS users choosing current-user-only installation; /usr/local/bin missing entirely.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/b435aea1eb110ffb. Report an issue: GitHub.