jdx/mise · error
brew-cask: refusing generic artifact copy outside Homebrew…
Error message
brew-cask: refusing generic artifact copy outside Homebrew prefix: {} What it means
validate_generic_copy_target gates the destination of a cask generic artifact copy. The target must start with the Homebrew prefix path, have at least one component strictly below it (the prefix itself is not a valid destination), and pass path_starts_with_resolved_root, which resolves symlinks so a target that only appears to sit under the prefix via a symlink is rejected. Anything else aborts the copy before data moves.
Solutions
- Change the artifact target to a path strictly under the Homebrew prefix, e.g. $HOMEBREW_PREFIX/etc/foo.conf
- For system locations (/Library, /etc), use the cask's preflight/postflight steps or a pkg artifact instead of a generic artifact
- If the file must exist elsewhere, install under the prefix and symlink from a postflight step
- Confirm the prefix mise uses (HOMEBREW_PREFIX / mise settings) matches what the cask assumed
Example fix
# before (cask stanza)
artifact "config.plist", target: "/Library/Preferences/com.example.plist"
# after
artifact "config.plist", target: "#{HOMEBREW_PREFIX}/etc/com.example.plist" Defensive patterns
Strategy: validation
Validate before calling
// Check a generic artifact target before installing.
fn target_under_prefix(target: &std::path::Path, prefix: &std::path::Path) -> bool {
target.is_absolute()
&& target.starts_with(prefix)
&& target.strip_prefix(prefix).map(|p| p.components().next().is_some()).unwrap_or(false)
&& std::fs::canonicalize(target)
.or_else(|_| std::fs::canonicalize(target.parent().unwrap_or(prefix)))
.map(|r| r.starts_with(prefix))
.unwrap_or(false)
} Type guard
fn is_safe_generic_target(target: &str, prefix: &Path) -> bool {
let expanded = target.replace("$HOMEBREW_PREFIX", &prefix.to_string_lossy());
target_under_prefix(Path::new(&expanded), prefix)
} Prevention
- Always express artifact targets as $HOMEBREW_PREFIX-relative paths
- Lint cask definitions for absolute targets outside the prefix before shipping them
- Remember the prefix itself is not a valid target — at least one component must follow it
When it happens
Trigger: A cask artifact target such as /Library/LaunchDaemons/x.plist, /etc/man.conf, ~/Library/x, a target equal to the prefix itself, or a target under the prefix whose symlinked ancestors resolve outside the prefix.
Common situations: Casks written for classic Homebrew ruby behavior that install launchd plists or system configs to absolute paths; prefix mismatch after moving between /usr/local and /opt/homebrew; assuming arbitrary absolute targets are supported.
Related errors
- brew-cask: generic artifact target
- brew-cask: app target
- brew-cask: binary target
- brew-cask: completion target
- brew-cask: generic artifact source is not contained by the…
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/69fd493e898b1bf4.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask.rs:2189
)?;
if bound.st_dev != linked.st_dev || bound.st_ino != linked.st_ino {
bail!("brew-cask: temporary artifact directory was replaced");
}
nix::unistd::unlinkat(
&parent.fd,
staging_name,
nix::unistd::UnlinkatFlags::RemoveDir,
)?;
Ok(())
}
fn validate_generic_copy_target(target: &Path) -> Result<()> {
let prefix = prefix::prefix();
if !target.starts_with(&prefix)
|| target.strip_prefix(&prefix)?.components().next().is_none()
|| !path_starts_with_resolved_root(target, &prefix)
{
bail!(
"brew-cask: refusing generic artifact copy outside Homebrew prefix: {}",
target.display()
);
}
Ok(())
}
#[cfg(unix)]
struct TrustedOperationParent {
fd: std::os::fd::OwnedFd,
}
#[cfg(unix)]
impl TrustedOperationParent {
fn path(&self) -> Result<PathBuf> {
#[cfg(target_os = "linux")]
return Ok(
Path::new("/proc/self/fd").join(std::os::fd::AsRawFd::as_raw_fd(&self.fd).to_string())View on GitHub (pinned to 6f52dcdf99)