Hmbown/CodeWhale · error

Refusing to replace managed/system path

Error message

Refusing to replace managed/system path {}.

{GITHUB_MIGRATION_HELP}

What it means

Codewhale's self-updater refuses to overwrite a target path that belongs to a package manager or system location (checked via InstallMethod::from_path and protected_update_path). Only the running binary and byte-identical copies of it are safe to replace; command names alone do not establish ownership of a sibling file. The message includes GITHUB_MIGRATION_HELP pointing the user at manual install/uninstall steps.

Solutions

  1. Uninstall the package-manager copy (brew uninstall / apt remove / cargo uninstall) and reinstall via the official installer script so the binary is self-update-managed.
  2. Run the updater against the real binary path, not an alias/symlink/wrapper.
  3. Follow the GITHUB_MIGRATION_HELP printed with the error to migrate installations manually.
  4. Check where `which -a codewhale` points and remove stale aliases in PATH.

Example fix

// before: self-update over a brew-managed binary fails
$ codewhale update
// after: let the package manager own updates
$ brew upgrade codewhale   # or: reinstall via official installer, then `codewhale update`
Defensive patterns

Strategy: validation

Validate before calling

use codewhale_cli::update::InstallMethod;
fn can_self_update(path: &std::path::Path) -> bool {
    InstallMethod::from_path(path).supports_self_update()
}
// run before `codewhale update`; if false, update via your package manager instead

Prevention

When it happens

Trigger: Running `codewhale update` (run_update) where the resolved update target resolves to a path owned by a package manager (brew, apt, cargo home, etc.), or a path on the protected list; also triggered by tests where an unrelated alias, a desktop-entry primary swap, or a foreign symlink sits in the install directory.

Common situations: User installed via Homebrew/apt/cargo and then runs the built-in updater; a wrapper script or symlink named like the binary exists in PATH; the updater resolves a desktop launcher or alias instead of the real binary.

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


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/5c38c5f73b01426a. Report an issue: GitHub.

Appendix: source

Thrown at crates/cli/src/update.rs:567

                identity.android_proof,
                fresh
            );
        }
    }
    let bytes = std::fs::read(&identity.path).context("failed to recheck updater binary")?;
    if sha256_hex(&bytes) != identity.file_hash {
        bail!(
            "The running executable path changed during the update; no further files were replaced. Run the intended executable again by its full path."
        );
    }
    Ok(())
}

/// Only the running binary and copies of those exact bytes are ours to update.
/// Command names alone do not establish ownership of an existing sibling.
fn validate_update_target(target: &Path, identity: &UpdateExecutableIdentity) -> Result<()> {
    if !InstallMethod::from_path(target).supports_self_update() || protected_update_path(target) {
        bail!(
            "Refusing to replace managed/system path {}.\n\n{GITHUB_MIGRATION_HELP}",
            target.display()
        );
    }
    let metadata = match std::fs::symlink_metadata(target) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound && target != identity.path => {
            return Ok(());
        }
        Err(error) => {
            return Err(error)
                .with_context(|| format!("failed to inspect update target {}", target.display()));
        }
    };
    if !metadata.is_file() || metadata.is_symlink() {
        bail!(
            "Refusing to replace {}: the update target is not a regular file.\n\n{GITHUB_MIGRATION_HELP}",
            target.display()

View on GitHub (pinned to 73e0f67d83)