gitbutlerapp/gitbutler · error
CLI destination must be absolute
Error message
CLI destination must be absolute
What it means
install_cli_link_escalated (macOS) creates a symlink to the CLI binary, possibly via an escalated privilege helper. Before doing anything it ensures the destination path is absolute; a relative destination cannot be safely resolved by the elevated helper, so it aborts with 'CLI destination must be absolute'.
Solutions
- Canonicalize the destination (std::fs::canonicalize or join onto '/') before calling the install API
- Pass a fully-qualified path like /usr/local/bin/but
- If building custom install tooling, resolve relative paths against the intended base directory first
Example fix
// before
install_cli_link_escalated(&cli, Path::new("bin/but"), policy)
// after
let dest = std::fs::canonicalize("bin/but").unwrap_or_else(|_| base.join("bin/but"));
install_cli_link_escalated(&cli, &dest, policy) Defensive patterns
Strategy: validation
Validate before calling
if !destination.is_absolute() {
return Err(anyhow!("destination must be absolute"));
}
// or: let destination = std::fs::canonicalize(destination_parent)?.join(file_name); Prevention
- Always canonicalize user-supplied install paths
- Never rely on CWD when invoking escalated helpers
- Default to known absolute prefixes (/usr/local/bin)
- Add is_absolute() asserts in install scripts
When it happens
Trigger: Calling the CLI install v2 flow with a destination path that is relative (e.g. 'bin/but' instead of '/usr/local/bin/but'), reaching the escalated link installer.
Common situations: Passing a user-supplied destination from config or a shell variable without canonicalizing it; running under a helper process whose CWD differs from the caller's.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- CLI source path must be absolute
- CLI source ' ' must be an executable file
- Aborting due to empty branch name
- Aborting due to empty
- Automatic CLI installation is not supported on Windows. To…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/73e0c9adc85d535b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-action/src/cli.rs:70
Err(InstallError::InstallationRequiresElevatedPrivileges(err)) => match mode {
InstallMode::AllowPrivilegeElevation => {
install_cli_link_escalated(cli_path, destination, symlink_policy)
}
InstallMode::CurrentUserOnly => Err(err)
.context("Privilege escalation required but not allowed under user install mode"),
},
Err(InstallError::Other(err)) => Err(err),
}
}
/// Installs the CLI link with escalated privileges.
#[cfg(any(target_os = "macos", all(test, unix)))]
fn install_cli_link_escalated(
cli_path: &std::path::Path,
destination: &std::path::Path,
symlink_policy: ExistingSymlinkPolicy,
) -> anyhow::Result<()> {
anyhow::ensure!(
destination.is_absolute(),
"CLI destination must be absolute"
);
let directory = destination
.parent()
.context("CLI destination has no parent")?;
// osascript accepts text arguments, so reject non-UTF-8 instead of changing paths.
let source = cli_path.to_str().context("CLI path is not valid UTF-8")?;
let target = destination
.to_str()
.context("CLI destination is not valid UTF-8")?;
let directory = directory
.to_str()
.context("CLI destination directory is not valid UTF-8")?;
// The script's destination check and `ln` are not atomic: if a directory appears at the
// destination between them, `ln` can create a link inside it. Verification below rejects that
// result, but the stray link remains. Avoiding this race would require an elevated helper
// calling symlink(2) directly instead of `ln`. That seems a bit overkill for now.View on GitHub (pinned to 58e5313667)