gitbutlerapp/gitbutler · error
Refusing to replace non-symlink file
Error message
Refusing to replace non-symlink file '{}' What it means
install_cli_link installs the CLI by creating a symlink at destination. Safety policy: if the destination exists and is a regular file (not a symlink), it refuses to overwrite it, returning InstallError::Other wrapping 'Refusing to replace non-symlink file {}'. This prevents clobbering a user's own binary.
Solutions
- Remove or rename the existing file at destination, then rerun the installer
- Allow the policy that permits replacing existing symlinks, or manually replace the file with a symlink to the bundled CLI
- Choose a different destination directory that doesn't already contain a 'but' file
- Check what owns the file (package manager) and uninstall it via that tool first
Example fix
// before $ but cli install # /usr/local/bin/but is a plain file // after $ sudo mv /usr/local/bin/but /usr/local/bin/but.bak $ but cli install
Defensive patterns
Strategy: validation
Validate before calling
if let Ok(md) = std::fs::symlink_metadata(destination) {
if !md.is_symlink() {
// pre-check: prompt user or move the file aside before install
}
} Prevention
- Check symlink_metadata at destination before installing
- Uninstall package-manager-owned 'but' binaries first
- Back up any existing plain file before removal
- Prefer dedicated install dirs to avoid collisions
When it happens
Trigger: Installing the CLI where destination already exists as a real file (not a symlink) and the symlink policy doesn't permit replacing it.
Common situations: A previous manual copy of the binary at /usr/local/bin/but; another tool (e.g. Homebrew, cargo install) owns the path; leftovers from an old non-symlink installer.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- Refusing to install symlink onto existing non-symlink at
- Automatic CLI installation is not supported on Windows. To…
- CLI destination must be absolute
- CLI source ' ' must be an executable file
- CLI source path must be absolute
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/fe69ce95c3575382.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-action/src/cli.rs:123
/// The install failed because of insufficient privileges - escalation recommended to proceed.
InstallationRequiresElevatedPrivileges(anyhow::Error),
/// Any other error - we don't act on this.
Other(anyhow::Error),
}
#[cfg(any(target_os = "macos", all(test, unix)))]
fn install_cli_link(
source: &std::path::Path,
destination: &std::path::Path,
symlink_policy: ExistingSymlinkPolicy,
) -> Result<(), InstallError> {
use std::fs;
validate_cli_source(source).map_err(InstallError::Other)?;
match fs::symlink_metadata(destination) {
Ok(metadata) => {
if !metadata.is_symlink() {
return Err(InstallError::Other(anyhow::anyhow!(
"Refusing to replace non-symlink file '{}'",
destination.display()
)));
}
let target = fs::read_link(destination)
.context("Cannot read existing CLI symlink")
.map_err(InstallError::Other)?;
if target == source || matches!(symlink_policy, ExistingSymlinkPolicy::Refuse) {
return verify_cli_link(source, destination).map_err(InstallError::Other);
}
fs::remove_file(destination)
.context("Cannot remove existing CLI symlink")
.map_err(escalate_privilege_error_if_permission_denied)?;
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => {
return Err(err)
.context("Cannot inspect CLI installation destination")View on GitHub (pinned to 58e5313667)