gitbutlerapp/gitbutler · error
CLI source ' ' must be an executable file
Error message
CLI source '{}' must be an executable file What it means
validate_cli_source also requires the source path to point to an existing, executable file: metadata must succeed, the entry must be a regular file, and its permission bits must include any execute bit (0o111). Otherwise it errors with 'CLI source {} must be an executable file'.
Solutions
- chmod +x the CLI binary at the source path
- Verify the path points to the actual built binary file, not a directory or missing artifact
- Rebuild the CLI (cargo build) if the binary is absent, then re-run install
- Fix packaging/extraction so execute permissions are preserved
Example fix
// before $ but cli install --source ./dist/but # not executable // after $ chmod +x ./dist/but $ but cli install --source ./dist/but
Defensive patterns
Strategy: validation
Validate before calling
let md = std::fs::metadata(source)?;
use std::os::unix::fs::PermissionsExt;
if !md.is_file() || md.permissions().mode() & 0o111 == 0 {
// chmod +x or rebuild the binary before install
} Prevention
- Verify the binary exists and is built before install
- Preserve exec bits when copying/archiving
- chmod +x after extracting from archives
- Point installers at the file, never a directory
When it happens
Trigger: install_cli_link given a source that is a directory, a non-executable file (missing +x), or a path that exists only as metadata-inaccessible; the ensure! fires when is_file() or the mode check fails.
Common situations: Pointing at a build artifact that wasn't built/failed to build; copying the binary without preserving permissions (losing +x); passing a directory or symlink-to-nothing; files extracted from archives that strip exec bits.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- CLI destination must be absolute
- CLI source path must be absolute
- 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/f37a24700a9d44b6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-action/src/cli.rs:178
/// Map the error to escalate privileges if permission was denied.
#[cfg(any(target_os = "macos", all(test, unix)))]
fn escalate_privilege_error_if_permission_denied(err: anyhow::Error) -> InstallError {
match err.downcast_ref::<std::io::Error>() {
Some(io_err) if io_err.kind() == std::io::ErrorKind::PermissionDenied => {
InstallError::InstallationRequiresElevatedPrivileges(err)
}
_ => InstallError::Other(err),
}
}
#[cfg(any(target_os = "macos", all(test, unix)))]
fn validate_cli_source(source: &std::path::Path) -> anyhow::Result<()> {
use std::os::unix::fs::PermissionsExt;
anyhow::ensure!(source.is_absolute(), "CLI source path must be absolute");
let metadata = std::fs::metadata(source)
.with_context(|| format!("Cannot access CLI executable at '{}'", source.display()))?;
anyhow::ensure!(
metadata.is_file() && metadata.permissions().mode() & 0o111 != 0,
"CLI source '{}' must be an executable file",
source.display()
);
Ok(())
}
#[cfg(any(target_os = "macos", all(test, unix)))]
fn verify_cli_link(source: &std::path::Path, destination: &std::path::Path) -> anyhow::Result<()> {
let target = std::fs::read_link(destination).context("Cannot read installed CLI symlink")?;
anyhow::ensure!(
target == source,
"Refusing to replace '{}', which points to '{}' instead of '{}'",
destination.display(),
target.display(),
source.display()
);
Ok(())View on GitHub (pinned to 58e5313667)