jdx/mise · error

brew-cask: refusing to remove generic artifact outside

Error message

brew-cask: refusing to remove generic artifact outside {}: {}

What it means

During cask upgrade/removal, mise deletes previously installed generic artifacts that are no longer part of the current cask version. Before removing a recorded target, it verifies the recorded path still resolves inside the brew prefix; if a record points outside the prefix (e.g. because the prefix changed or the path was tampered with), mise refuses to delete it rather than removing arbitrary user files.

Solutions

  1. Manually remove the out-of-prefix artifact listed in the message, then re-run the cask uninstall/upgrade.
  2. Reinstall the cask under the current prefix so target records are regenerated with valid paths.
  3. Fix symlinks at the recorded path that resolve outside the prefix (relink them inside or delete them).
  4. Ensure HOMEBREW_PREFIX / mise prefix configuration matches what was used when the artifacts were installed.
  5. Inspect and clean stale target records from the previous install before upgrading.

Example fix

// before: record points to absolute legacy path
/usr/local/share/mytool/data.bin (recorded, prefix now /opt/homebrew)
// after: remove manually or relink under current prefix
rm /usr/local/share/mytool/data.bin   # then: mise install of cask proceeds
Defensive patterns

Strategy: try-catch

Validate before calling

let resolved = std::fs::canonicalize(&record.path)?;
if !resolved.starts_with(&prefix) {
    eprintln!("stale artifact outside prefix, remove manually: {}", record.path.display());
}

Type guard

fn record_inside_prefix(p: &std::path::Path, prefix: &std::path::Path) -> bool {
    std::fs::canonicalize(p).map(|p| p.starts_with(prefix)).unwrap_or(false)
}

Try / catch

match result {
    Err(e) if e.to_string().contains("refusing to remove generic artifact") => {
        // remove the out-of-prefix file manually, then retry
    }
    r => r?,
}

Prevention

When it happens

Trigger: Uninstalling or upgrading a cask when a recorded generic-artifact target from a previous install resolves outside the current prefix; the brew prefix was changed or migrated between installs; the target path was replaced by a symlink escaping the prefix; stale/corrupted target records.

Common situations: Users migrating Homebrew from /usr/local to /opt/homebrew (or custom prefixes) then upgrading casks; manually symlinked artifact targets; leftover records from an older mise/brew layout.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


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

Appendix: source

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

    };
    Ok(receipt
        .targets
        .into_iter()
        .filter(|record| receipt.generic.contains(&record.path))
        .collect())
}

fn remove_obsolete_generic_artifacts(
    previous_targets: &[CaskTargetRecord],
    current_targets: &[PathBuf],
) -> Result<()> {
    let prefix = prefix::prefix();
    for record in previous_targets {
        if current_targets.contains(&record.path) || !cask_target_record_matches(record)? {
            continue;
        }
        if !path_starts_with_resolved_root(&record.path, &prefix) {
            bail!(
                "brew-cask: refusing to remove generic artifact outside {}: {}",
                prefix.display(),
                record.path.display()
            );
        }
        if let Err(err) = remove_trusted_generic_target(&record.path) {
            warn!(
                "brew-cask: leaving obsolete generic artifact {} because its parent directories are mutable: {err:#}",
                record.path.display()
            );
        }
    }
    Ok(())
}

fn remove_trusted_generic_target(target: &Path) -> Result<()> {
    let expected_parent = resolved_parent(target)?;
    match remove_trusted_generic_target_from(target, &expected_parent) {

View on GitHub (pinned to 533346cc37)