jdx/mise · error

brew-cask: refusing operation through a changed generic…

Error message

brew-cask: refusing operation through a changed generic artifact parent: {}

What it means

validate_trusted_operation_parent re-canonicalizes the path of the directory fd opened by open_trusted_operation_parent and compares it to the parent recorded when the transaction started. A mismatch means the directory now reachable at that path is not the same one bound earlier (renamed, recreated, or reached through a changed symlink such as the prefix itself), so the pending rename/remove against the held fd is refused — a TOCTOU guard on generic artifact backup/rename operations.

Solutions

  1. Re-run the cask operation once the concurrent change has settled
  2. Stop overlapping mise/brew runs against the same prefix — serialize them with a lock
  3. If you deliberately changed a prefix symlink, finish or uninstall pending casks first, then retry
  4. If it recurs without concurrency, audit what renames the printed parent path (cleanup daemons, overlay fs) and report it

Example fix

# before: concurrent mutation
$ brew reinstall foo & mise brew install --cask bar &

# after: serialized
$ brew reinstall foo && mise brew install --cask bar
Defensive patterns

Strategy: retry

Validate before calling

// Snapshot the parent identity before the operation and compare after.
let before = std::fs::canonicalize(parent_dir)?;
// ... run install ...
let after = std::fs::canonicalize(parent_dir)?;
assert_eq!(before, after, "parent changed during operation");

Try / catch

match run_install() {
    Err(e) if e.to_string().contains("changed generic artifact parent") => {
        // directory was retargeted mid-flight: settle the environment, retry
        retry_after_concurrency_settles()
    }
    other => other,
}

Prevention

When it happens

Trigger: The artifact parent directory or a symlinked ancestor (the prefix) is renamed or retargeted while an install transaction is in flight; concurrent mise/brew operations restructuring the prefix subtree between the trusted walk and the mutation.

Common situations: Two package managers mutating /opt/homebrew concurrently; a prefix migration happening mid-operation; parallel CI jobs racing cask installs against the same prefix.

Related errors


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

Appendix: source

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

            .file_name()
            .ok_or_else(|| eyre!("brew-cask: generic artifact target has no filename"))?;
        remove_all_at(&parent.fd, name)
    }
    #[cfg(not(unix))]
    {
        let _ = expected_parent;
        file::remove_all(target)
    }
}

#[cfg(unix)]
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"))?;

View on GitHub (pinned to 6f52dcdf99)