jdx/mise · error

brew-cask: generic artifact backup changed directories

Error message

brew-cask: generic artifact backup changed directories

What it means

When mise backs up or restores a generic cask artifact via rename, it requires that both source and target live in the same directory (rename cannot cross filesystems/directories). If from.parent() differs from to.parent(), the backend refuses rather than silently falling back to an unsafe copy. This keeps backup/restore of trusted targets inside the validated parent directory.

Solutions

  1. Reinstall the cask so artifact and backup paths are recomputed consistently under the same parent directory.
  2. Ensure the computed backup/restore path is in the same directory as the artifact (same parent) before triggering upgrade/rollback.
  3. Clear stale artifact records from a previous prefix layout and reinstall.
  4. Report the internal inconsistency if it persists with default configuration — it indicates a backend path-computation bug.

Example fix

// before
let backup = prefix.join("backups").join(name);   // different parent
// after
let backup = artifact.parent().unwrap().join(format!("{}.bak", name));  // same parent
Defensive patterns

Strategy: try-catch

Validate before calling

if backup.parent() != artifact.parent() {
    eprintln!("backup path must share the artifact's parent directory");
    std::process::exit(1);
}

Type guard

fn same_parent(a: &std::path::Path, b: &std::path::Path) -> bool {
    a.parent() == b.parent()
}

Prevention

When it happens

Trigger: rename_trusted_generic_target is called with 'from' and 'to' paths whose parent directories differ; a backup path is computed in a different directory than the artifact; the source has no parent (root-level path).

Common situations: Internal misconfiguration where the backup dir differs from the artifact dir; artifacts recorded in a stale location from an older install so the computed backup path lands elsewhere; prefix migrations changing directory layouts between record time and operation time.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/60d76e5bb70f72ba. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask/mod.rs:2549

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 533346cc37)