jdx/mise · error

brew-cask: refusing operation through a changed generic arti

Error message

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

What it means

Rename/unlink helpers for generic artifacts open the parent directory as a descriptor at transaction start and, right before acting, canonicalize the descriptor's path and compare it with the expected parent recorded earlier. A mismatch means the directory reachable at that name was replaced or moved mid-operation, so the rename is refused rather than performed through an unexpected directory.

Source

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

            .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 9dcfcaa0dc)

Solutions

  1. Retry the cask operation once no other process is modifying that parent directory
  2. Remove and recreate the parent directory cleanly (with correct ownership), then re-run the install
  3. Serialize automation so only one installer manipulates the target tree at a time
Defensive patterns

Strategy: retry

Validate before calling

use std::fs;

// Before the operation, confirm the parent still resolves to the expected directory
// and is not world-writable (cheap pre-flight for the identity check)
fn parent_matches_expected(parent: &std::path::Path, expected: &std::path::Path) -> bool {
    fs::canonicalize(parent).is_ok_and(|p| p == expected)
}

Try / catch

match rename_trusted_generic_target(from, to, expected_parent) {
    Err(e) if e.to_string().contains("changed generic artifact parent") => {
        // the parent dir was replaced mid-run: recreate a clean parent and retry once
        std::thread::sleep(std::time::Duration::from_secs(1));
        rename_trusted_generic_target(from, to, expected_parent)?
    }
    other => other?,
}

Prevention

When it happens

Trigger: The parent directory of a generic artifact is deleted and recreated, renamed, or re-symlinked between the start of the artifact operation and the rename/restore step; concurrent installers writing the same directory.

Common situations: Parallel package managers or manual file management touching ~/Applications or the prefix while a cask install runs; a crashed prior attempt leaving a replacement directory; automation scripts that rebuild directories mid-install.

Related errors


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