jdx/mise · error

brew-cask: generic artifact target '{}' must stay below {}

Error message

brew-cask: generic artifact target '{}' must stay below {}

What it means

generic_artifact_target_path expands $HOMEBREW_PREFIX in the cask's target string and validates the result: it must be absolute, lexically under the prefix with at least one component beneath it, free of '..' components, and its symlink-resolved root must still be under the prefix. Any violation refuses with the offending target shown.

Source

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

        run_installer_artifact(stage, installer, targets.copied_files())?;
        completed(index)?;
    }
    durabilize_staged_symlink_targets(stage, temporary_caskroom, targets)
}

fn generic_artifact_target_path(target: &str) -> Result<PathBuf> {
    let prefix = prefix::prefix();
    let expanded = target.replace("$HOMEBREW_PREFIX", &prefix.to_string_lossy());
    let target = PathBuf::from(expanded);
    if !target.is_absolute()
        || !target.starts_with(&prefix)
        || target.strip_prefix(&prefix)?.components().next().is_none()
        || target
            .components()
            .any(|component| matches!(component, Component::ParentDir))
        || !path_starts_with_resolved_root(&target, &prefix)
    {
        bail!(
            "brew-cask: generic artifact target '{}' must stay below {}",
            target.display(),
            prefix.display()
        );
    }
    Ok(target)
}

fn generic_artifact_targets(artifacts: &CaskArtifacts) -> Result<Vec<PathBuf>> {
    artifacts
        .generic
        .iter()
        .map(|artifact| generic_artifact_target_path(&artifact.target))
        .collect()
}

fn previous_generic_targets(cask: &Cask) -> Result<Vec<CaskTargetRecord>> {
    let Some(version) = installed_version(&cask.token) else {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Write the target as an absolute path beginning with $HOMEBREW_PREFIX ("$HOMEBREW_PREFIX/Library/Foo")
  2. Remove '..' components from the target string
  3. Make the prefix a real directory or ensure its symlinks resolve consistently so the resolved-root check passes

Example fix

# before
target: "Library/Application Support/Foo"

# after
target: "$HOMEBREW_PREFIX/Library/Application Support/Foo"
Defensive patterns

Strategy: validation

Validate before calling

use std::path::{Path, Component};

fn target_string_is_valid(target: &str, prefix: &Path) -> bool {
    let expanded = target.replace("$HOMEBREW_PREFIX", &prefix.to_string_lossy());
    let t = Path::new(&expanded);
    t.is_absolute()
        && t.starts_with(prefix)
        && t.strip_prefix(prefix).is_ok_and(|r| r.components().next().is_some())
        && !t.components().any(|c| matches!(c, Component::ParentDir))
}

Prevention

When it happens

Trigger: A target string that is relative ("Library/Foo"), absolute outside the prefix ("/Library/Foo"), contains '..', or where the prefix itself is a symlink so the resolved root no longer matches (path_starts_with_resolved_root fails).

Common situations: Casks hardcoding absolute macOS paths; HOMEBREW_PREFIX pointed at a symlinked directory; target templates missing the $HOMEBREW_PREFIX variable.

Related errors


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