jdx/mise · error

brew-cask: invalid appdir '{}'

Error message

brew-cask: invalid appdir '{}'

What it means

A cask `binary` target written as `$APPDIR/<rel>` is joined to the appdir in use; that appdir must be one of the allowed app-dir roots (`/Applications` by default, the APP_DIR override, or the Homebrew prefix's Applications dir). `binary_target_path` rejects a `$APPDIR/...` target when the supplied appdir is not in that allowlist, preventing binaries from being linked into arbitrary directories via the appdir parameter.

Source

Thrown at src/system/packages/brew/cask/paths.rs:276

}

pub(super) fn allowed_binary_target_roots_display(roots: &[PathBuf]) -> String {
    roots
        .iter()
        .map(|root| root.display().to_string())
        .collect::<Vec<_>>()
        .join(" or ")
}

pub(super) fn binary_target_path(target_name: &str, appdir: &Path) -> Result<PathBuf> {
    if target_name.contains('\0') {
        bail!("brew-cask: binary target contains NUL");
    }
    if let Some(relative) = target_name.strip_prefix("$APPDIR/") {
        let relative = Path::new(relative);
        reject_appdir_escape(relative, "binary $APPDIR target", target_name)?;
        if !allowed_appdir_roots()?.iter().any(|root| root == appdir) {
            bail!("brew-cask: invalid appdir '{}'", appdir.display());
        }
        return Ok(appdir.join(relative));
    }
    if target_name.contains("$APPDIR") {
        bail!("brew-cask: $APPDIR must prefix a binary target");
    }
    let prefix = prefix::prefix();
    let prefix_str = prefix.to_string_lossy();
    let target_name = target_name.replace("$HOMEBREW_PREFIX", prefix_str.as_ref());
    let path = PathBuf::from(&target_name);
    let target = if path.is_absolute() {
        path
    } else if target_name.contains('/') {
        prefix.join(path)
    } else {
        prefix.join("bin").join(path)
    };
    if target

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Pass the appdir obtained from mise's own resolution (the `cask_appdir`/`target_app_dir` value) rather than an arbitrary directory
  2. Ensure the APP_DIR env override, if set, is a valid absolute path so it lands in `allowed_appdir_roots()`
  3. Rewrite the cask target to a prefix-anchored path (`$HOMEBREW_PREFIX/bin/...`) if it is not meant to live in the appdir
  4. Update the calling code so `$APPDIR` targets are only evaluated against allowed appdir roots

Example fix

// before (Rust caller)
binary_target_path("$APPDIR/bin/tool", &staging_dir)?
// after
let appdir = cask_appdir()?;
binary_target_path("$APPDIR/bin/tool", &appdir)?
Defensive patterns

Strategy: validation

Validate before calling

fn appdir_allowed(appdir: &std::path::Path) -> bool {
    appdir == std::path::Path::new("/Applications")
        || appdir == std::path::Path::new("/opt/homebrew/Applications")
        || appdir == std::path::Path::new("/usr/local/Applications")
}

Try / catch

match result {
    Err(e) if e.to_string().contains("invalid appdir") => {
        eprintln!("pass an allowed appdir root for $APPDIR binary targets");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling `binary_target_path(target, appdir)` (via `target_path` or `binary_targets_must_stay_under_an_allowed_root`) with a target starting `$APPDIR/` and an `appdir` argument not equal to any entry of `allowed_appdir_roots()` — e.g. a caller passing a staging path or a custom directory as appdir.

Common situations: Custom cask tooling passing its own directory as appdir; an APP_DIR override typo making the computed appdir mismatch the allowed roots; stale code assuming any directory works with `$APPDIR` targets.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/75f3d1f0f416bfea. Report an issue: GitHub.