gitbutlerapp/gitbutler · error
CLI installation failed
Error message
CLI installation failed ({}): {} What it means
check_cli_install_output validates the result of the macOS AppleScript that installs the CLI symlink with administrator privileges. When the elevated shell script exits non-zero, this error reports the exit status plus the script's trimmed stderr. It wraps real failures of mkdir/rm/ln inside the privileged script (e.g. the destination already exists and symlinkPolicy is not replace, or rm/ln failed).
Solutions
- Read the stderr in the error message; it names which command inside the script failed.
- If the destination exists as a regular file/directory, remove or move it: `sudo mv /usr/local/bin/but /usr/local/bin/but.bak`, then retry.
- Retry the install choosing the Replace symlink policy if you want the existing symlink overwritten.
- Check permissions on the target directory: `ls -ld /usr/local/bin` and ensure it is writable by root.
- If the authorization dialog itself failed, rerun the install and approve the administrator prompt.
Example fix
// before: destination occupied by a regular file Error: CLI installation failed (exit status: 1): CLI destination already exists // after $ sudo mv /usr/local/bin/but /usr/local/bin/but.bak $ retry install -> stdout 'gitbutler-cli-installed'
Defensive patterns
Strategy: try-catch
Validate before calling
let dest = "/usr/local/bin/but";
if std::path::Path::new(dest).exists() && !std::path::Path::new(dest).is_symlink() {
eprintln!("{dest} exists and is not a symlink; move it aside before installing");
} Try / catch
match install_cli_link_escalated(...) {
Err(e) => {
let msg = format!("{e:#}");
if msg.contains("CLI installation failed") {
// inspect stderr portion, clear the destination or fix permissions, then retry
}
}
ok => {}
} Prevention
- Ensure the destination path is free (or a replaceable symlink) before starting an elevated install.
- Verify /usr/local/bin exists and is root-writable.
- Do not wrap or filter the installer script's stdout/stderr.
When it happens
Trigger: install_cli_link_escalated runs the INSTALL_CLI_SCRIPT AppleScript with administrator privileges and the underlying shell command fails: destination exists and is not a replaceable symlink ('CLI destination already exists'), /bin/rm of an old symlink fails, /bin/ln -s -h fails, or /bin/mkdir -p of the target directory fails.
Common situations: A regular file or directory already occupies /usr/local/bin/but so ln fails; the admin script was denied mid-way leaving inconsistent state; a read-only or odd-permission /usr/local/bin; an old GitButler version left an entry the 'keep' symlink policy refuses to touch.
Understand the failure class
Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.
Related errors
- CliInstallCancelled
- Refusing to replace ' ', which points to ' ' instead of
- Unexpected CLI installer response
- A 'but' binary already exists at
- command exited with status
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/379cdd6968f66613.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-action/src/cli.rs:225
do shell script ("/bin/mkdir -p " & targetDirectory & " || exit $?; " & ¬
"if [ -L " & targetPath & " ] && [ " & symlinkPolicy & " = replace ]; then " & ¬
"/bin/rm " & targetPath & " || exit $?; " & ¬
"elif [ -e " & targetPath & " ] || [ -L " & targetPath & " ]; then " & ¬
"echo 'CLI destination already exists' >&2; exit 1; fi; " & ¬
"/bin/ln -s -h " & sourcePath & " " & targetPath) ¬
with prompt "GitButler needs administrator access to install but into /usr/local/bin." ¬
with administrator privileges
return "gitbutler-cli-installed"
on error messageText number errorNumber
if errorNumber is -128 then return "gitbutler-cli-install-cancelled"
error messageText number errorNumber
end try
end run
"#;
#[cfg(any(target_os = "macos", all(test, unix)))]
fn check_cli_install_output(output: std::process::Output) -> anyhow::Result<()> {
anyhow::ensure!(
output.status.success(),
"CLI installation failed ({}): {}",
output.status,
String::from_utf8_lossy(&output.stderr).trim()
);
if output.stdout == b"gitbutler-cli-install-cancelled\n" {
return Err(
anyhow!("Administrator authorization was cancelled").context(ErrorContext::new_static(
Code::CliInstallCancelled,
"CLI install cancelled",
)),
);
}
anyhow::ensure!(
output.stdout == b"gitbutler-cli-installed\n",
"Unexpected CLI installer response"
);
Ok(())View on GitHub (pinned to 58e5313667)