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 targetView on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Unset MISE_BREW_CASK_OPT_APPDIR (or set it to a canonical, non-symlinked absolute dir) so the caller and allowlist agree
- Run `realpath` on the override and export that resolved value
- Ensure HOMEBREW_PREFIX is stable across the mise invocation
- 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
- Keep MISE_BREW_CASK_OPT_APPDIR stable and canonical (realpath'd) for the whole mise run
- Do not change HOMEBREW_PREFIX between planning and linking cask binaries
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
- brew-cask: {APP_DIR_ENV} '{}' must be an absolute path
- brew-cask: {APP_DIR_ENV} '{}' must not contain '..'
- brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesy
- brew-cask: app target '{target_name}' must be under {}
- install from exe
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/2d862e29a2191081.
Report an issue: GitHub.