jdx/mise · error

brew-cask: generic artifact backup changed directories

Error message

brew-cask: generic artifact backup changed directories

What it means

rename_trusted_generic_target atomically renames a generic artifact target to its backup name using renameat against a single trusted parent directory fd. It first requires from.parent() == to.parent() — the backup must live in the same directory as the target so the rename never crosses directories. Because the backup is normally derived via target.with_extension("mise-old-<hash>") (which preserves the parent), a mismatch is an internal-invariant breach: the two paths were built from different bases.

Solutions

  1. If calling this from code, derive the backup with target.with_extension(...) so it shares the target's parent
  2. Re-run once to rule out a transient filesystem quirk
  3. If it persists, capture it with MISE_DEBUG=1 (or RUST_BACKTRACE=1) and file an issue — the invariant should hold for every validated target

Example fix

// before (caller code)
let backup = backups_dir.join(target.file_name().unwrap());
rename_trusted_generic_target(&target, &backup, &parent)?;

// after
let backup = target.with_extension(format!("mise-old-{}", hash));
rename_trusted_generic_target(&target, &backup, &parent)?;
Defensive patterns

Strategy: validation

Validate before calling

// If you call rename_trusted_generic_target yourself, keep both paths in one dir.
let backup = target.with_extension(format!("mise-old-{}", short_hash(&target)));
assert_eq!(target.parent(), backup.parent());

Prevention

When it happens

Trigger: Programmatic callers passing a backup path located in a different directory; path normalization that changes the computed parent (trailing separators, surviving '..' components); not a configuration-level error for end users.

Common situations: Forks or custom tooling that construct backup paths themselves; essentially unseen in normal CLI-driven installs.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/bc6ff41f56672156. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask.rs:2614

fn validate_trusted_operation_parent(
    parent: &TrustedOperationParent,
    expected_parent: &Path,
) -> Result<()> {
    let actual_parent = std::fs::canonicalize(parent.path()?)?;
    if actual_parent != expected_parent {
        bail!(
            "brew-cask: refusing operation through a changed generic artifact parent: {}",
            expected_parent.display()
        );
    }
    Ok(())
}

fn rename_trusted_generic_target(from: &Path, to: &Path, expected_parent: &Path) -> Result<()> {
    #[cfg(unix)]
    {
        if from.parent() != to.parent() {
            bail!("brew-cask: generic artifact backup changed directories");
        }
        let parent = open_trusted_operation_parent(from, true, false)?;
        validate_trusted_operation_parent(&parent, expected_parent)?;
        let from_name = from
            .file_name()
            .ok_or_else(|| eyre!("brew-cask: generic artifact source has no filename"))?;
        let to_name = to
            .file_name()
            .ok_or_else(|| eyre!("brew-cask: generic artifact target has no filename"))?;
        nix::fcntl::renameat(&parent.fd, from_name, &parent.fd, to_name)?;
        Ok(())
    }
    #[cfg(not(unix))]
    {
        let _ = expected_parent;
        file::rename(from, to)
    }
}

View on GitHub (pinned to 6f52dcdf99)