jdx/mise · error
brew-cask: generic artifact target
Error message
brew-cask: generic artifact target '{}' must stay below {} What it means
mise's brew-cask backend validates that a generic (non-app, non-font) artifact's install target stays under the mise/brew prefix. The target must not traverse upward with '..' components, must have a component below the prefix, and must resolve inside the prefix root after path resolution. Otherwise installation is refused, preventing cask artifact declarations from writing files into arbitrary system locations.
Solutions
- Change the artifact target to a path relative to and below the configured prefix (e.g. 'share/fonts/...' not '/Library/Fonts').
- Remove any '..' components from the target path.
- Verify your brew prefix configuration; if you use a custom prefix, ensure artifact targets are defined relative to it.
- Use a dedicated cask or mise feature (e.g. font artifacts) for locations outside the prefix instead of generic artifacts.
- Update the cask to a newer revision where the upstream fixed the target path.
Example fix
// before artifact target: "/Library/Scripts/mytool" // after artifact target: "share/scripts/mytool"
Defensive patterns
Strategy: validation
Validate before calling
let target = std::path::PathBuf::from(declared_target);
if target.components().any(|c| c == std::path::Component::ParentDir)
|| target.is_absolute()
{
eprintln!("artifact target must be relative and below the prefix");
std::process::exit(1);
} Type guard
fn stays_below_prefix(t: &std::path::Path, prefix: &std::path::Path) -> bool {
!t.components().any(|c| c == std::path::Component::ParentDir)
&& t.strip_prefix(prefix).is_ok()
} Prevention
- Declare generic artifact targets as prefix-relative paths.
- Never use '..' in artifact targets.
- Match your prefix configuration with the cask's expected layout.
- Use font/app artifact kinds for locations outside the prefix instead of forcing generic artifacts.
When it happens
Trigger: A cask declares a generic artifact whose target path contains '..' components; the target is an absolute path outside the prefix; path_starts_with_resolved_root fails because the target resolves (via symlinks) outside the prefix; a malformed artifact target with no components under the prefix.
Common situations: Hand-written or third-party casks with targets like '/Library/Fonts' or '~/bin' outside the managed prefix; prefix relocations (custom HOMEBREW_PREFIX) making previously valid relative targets resolve outside; users editing cask stanza targets to redirect artifacts.
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
- brew-cask: refusing to remove generic artifact outside
- brew-cask: ' ' must not contain '..
- brew-cask: app target
- brew-cask: binary target
- brew-cask: completion target
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/fe0072ba0324e60e.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask/mod.rs:2440
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)
}
/// The receipt of the currently installed version, if there is one.
fn previous_receipt(cask: &Cask) -> Result<Option<CaskReceipt>> {
let Some(version) = installed_version(&cask.token) else {
return Ok(None);
};
read_receipt(&caskroom_version_dir(&cask.token, &version))
}
fn previous_generic_targets(cask: &Cask) -> Result<Vec<CaskTargetRecord>> {
let Some(receipt) = previous_receipt(cask)? else {View on GitHub (pinned to 533346cc37)