gitbutlerapp/gitbutler · warning
Would probably need to run "ln -sf
Error message
Would probably need to run "ln -sf '{}' '{UNIX_LINK_PATH}'"{privilege} What it means
Returned by the CLI installer when it cannot create the /usr/local/bin/but symlink itself and falls back to instructing the user. It is reached when the earlier direct symlink attempts failed (no permission on /usr/local/bin), the platform is not macOS, or InstallMode::CurrentUserOnly disallows elevation. The message contains the exact ln -sf command to run, optionally suffixed with ' with root permissions'.
Solutions
- Run the exact command from the error message: sudo ln -sf '<printed-cli-path>' /usr/local/bin/but
- Or install into a directory you own that is on PATH: mkdir -p ~/bin && ln -sf '<cli>' ~/bin/but
- On macOS, retry with AllowPrivilegeElevation so the osascript path performs the privileged link
- Verify the cli binary exists at the printed path before linking (dev builds: build the but binary first)
Example fix
# before: install returns "Would probably need to run \"ln -sf ...\"" # after: run the printed command sudo ln -sf '/path/to/but' /usr/local/bin/but # or user-local alternative: mkdir -p ~/bin && ln -sf '/path/to/but' ~/bin/but echo 'export PATH="$HOME/bin:$PATH"' >> ~/.profile
Defensive patterns
Strategy: fallback
Validate before calling
// can we write /usr/local/bin without elevation?
let probe = std::path::Path::new("/usr/local/bin/.probe");
let unprivileged = std::fs::write(probe, b"").is_ok();
let _ = std::fs::remove_file(probe);
if !unprivileged {
// expect the manual-instruction error; guide the user up front
} Try / catch
match do_install_cli(mode) {
Err(e) if e.to_string().contains("Would probably need to run") => {
// extract the ln -sf command from the message and run/guide it
}
r => r,
} Prevention
- Pre-create the symlink in deployment scripts
- Prefer a user-writable PATH dir in locked-down environments
- Check the but binary exists before triggering install
When it happens
Trigger: do_install_cli on Linux (any mode), or on macOS with CurrentUserOnly: the non-privileged std::os::unix::fs::symlink into /usr/local/bin fails with permission denied, control falls past the osascript branch, and the else-branch returns this instruction error.
Common situations: Linux systems where /usr/local/bin is root-owned; macOS users choosing 'current user only'; headless or automation contexts where no interactive privilege prompt is possible; dev builds where the but binary does not exist yet (that trips a different message first).
Related errors
- osascript exited with status
- A 'but' binary already exists at
- Bitbucket repository
- BUG: we do not create or work with symlinks
- Cannot write to ( )
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/b435aea1eb110ffb.
Report an issue: GitHub.
Appendix: 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 caf1f223d3)