jdx/mise · error · eyre::Report

brew-cask: binary target '{}' must be under {}

Error message

brew-cask: binary target '{}' must be under {}

What it means

After expansion and anchoring, a binary target must fall under an allowed root: allowed_binary_target_roots() returns the Homebrew prefix plus /usr/local (src/system/packages/brew/cask.rs:6097). This bail fires for absolute targets pointing anywhere else — mise deliberately refuses to link cask binaries into system locations like /usr/bin or /Library.

Source

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

    let target = if path.is_absolute() {
        path
    } else if target_name.contains('/') {
        prefix.join(path)
    } else {
        prefix.join("bin").join(path)
    };
    if target
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        bail!(
            "brew-cask: binary target '{}' must not contain '..'",
            target.display()
        );
    }
    let roots = allowed_binary_target_roots();
    if !roots.iter().any(|root| target.starts_with(root)) {
        bail!(
            "brew-cask: binary target '{}' must be under {}",
            target.display(),
            allowed_binary_target_roots_display(&roots)
        );
    }
    Ok(target)
}

fn installed_version(token: &str) -> Option<String> {
    let versions = installed_versions(token);
    match versions.as_slice() {
        [version] => Some(version.clone()),
        [] => None,
        _ => {
            warn!("brew-cask:{token}: multiple Caskroom versions found; reinstall to reconcile");
            None
        }
    }

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Change the binary target to a prefix-relative form: bare "tool" (lands in <prefix>/bin) or "$HOMEBREW_PREFIX/bin/tool"
  2. Check `echo $HOMEBREW_PREFIX` — the allowed roots are exactly that prefix plus /usr/local
  3. Report the target upstream if a published cask declares an out-of-root binary
  4. Install the cask via `brew install --cask` if you need Homebrew's own (looser) behavior

Example fix

// before (cask binary target)
"target": "/usr/bin/mytool"
// after
"target": "mytool"
Defensive patterns

Strategy: validation

Validate before calling

use std::path::{Path, PathBuf};
fn binary_target_within_roots(name: &str, prefix: &Path) -> bool {
    let expanded = name.replace("$HOMEBREW_PREFIX", &prefix.to_string_lossy());
    let path = PathBuf::from(&expanded);
    let target = if path.is_absolute() {
        path
    } else if expanded.contains('/') {
        prefix.join(path)
    } else {
        prefix.join("bin").join(path)
    };
    let mut roots = vec![prefix.to_path_buf()];
    let usr_local = PathBuf::from("/usr/local");
    if prefix != usr_local { roots.push(usr_local); }
    roots.iter().any(|r| target.starts_with(r))
}

Try / catch

match binary_target_path(name, &appdir) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("must be under") => {
        warn!("cask binary target outside allowed roots: {name}");
        continue;
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: A cask binary target that is absolute and outside <HOMEBREW_PREFIX> and /usr/local, e.g. "/usr/bin/tool" or "/opt/custom/bin/tool"; or a non-standard HOMEBREW_PREFIX that changes which roots are allowed.

Common situations: Casks declaring system-path binaries; users with custom Homebrew prefixes on Apple Silicon (/opt/homebrew) hitting casks written for /usr/local; third-party taps with unusual targets.

Related errors


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