jdx/mise · error · eyre::Report

brew-cask: invalid appdir '{}'

Error message

brew-cask: invalid appdir '{}'

What it means

For $APPDIR binary targets, mise cross-checks the appdir it was handed against allowed_appdir_roots() — [/Applications, target_app_dir() (the resolved MISE_BREW_CASK_OPT_APPDIR override), and <HOMEBREW_PREFIX>/Applications] (src/system/packages/brew/cask.rs:6107). This bail means the appdir argument is none of those, i.e. an internal inconsistency between how the caller computed the appdir and how the allowlist computes it (for example a non-canonicalized or differently-resolved path).

Source

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

        .collect::<Vec<_>>()
        .join(" or ")
}

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);
        if relative.components().next().is_none()
            || relative
                .components()
                .any(|component| !matches!(component, Component::Normal(_)))
        {
            bail!("brew-cask: binary $APPDIR target '{target_name}' must stay below Applications");
        }
        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 9dcfcaa0dc)

Solutions

  1. Unset MISE_BREW_CASK_OPT_APPDIR (or set it to a canonical, non-symlinked absolute dir) so the caller and allowlist agree
  2. Run `realpath` on the override and export that resolved value
  3. Ensure HOMEBREW_PREFIX is stable across the mise invocation
  4. Update mise — mismatches like this are treated as bugs and fixed

Example fix

# before
export MISE_BREW_CASK_OPT_APPDIR=~/MyApps   # ~/MyApps is a symlink
# after
export MISE_BREW_CASK_OPT_APPDIR="$(realpath ~/MyApps)"
Defensive patterns

Strategy: validation

Validate before calling

use std::path::PathBuf;
fn appdir_in_allowlist(appdir: &std::path::Path, prefix: &std::path::Path) -> bool {
    let mut roots = vec![PathBuf::from("/Applications")];
    for root in [appdir_resolved_from_env(), prefix.join("Applications")] {
        if !roots.contains(&root) { roots.push(root); }
    }
    roots.iter().any(|r| r == appdir)
}

Try / catch

match binary_target_path(name, &appdir) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("invalid appdir") => {
        let canonical = appdir.canonicalize().unwrap_or(appdir.to_path_buf());
        binary_target_path(name, &canonical)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: binary_target_path called with an appdir that is not the default /Applications, the resolved env override, or <prefix>/Applications — e.g. the caller passed a symlinked or un-canonicalized spelling while the allowlist holds canonical paths.

Common situations: MISE_BREW_CASK_OPT_APPDIR pointing at a symlinked directory that resolves differently at plan time vs. link time; HOMEBREW_PREFIX changing mid-operation; custom code inside mise forks passing an arbitrary appdir.

Related errors


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