jdx/mise · error

brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesy

Error message

brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root

What it means

The brew-cask app-dir override must resolve to a real directory, not the filesystem root `/`. Because the resolved appdir is the containment boundary for privileged cask mutations, allowing `/` would let app targets be placed anywhere on disk. After resolving symlinks and normalizing (`/`, `//`, `/.`, symlink-to-`/`), `target_app_dir` rejects any value with no normal components.

Source

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

    {
        bail!(
            "brew-cask: {APP_DIR_ENV} '{}' must not contain '..'",
            dir.display()
        );
    }
    // Resolve the override to a real absolute path: canonicalize its longest
    // existing prefix and re-append the components that do not exist yet. This
    // makes the appdir a symlink-free containment boundary — privileged cask
    // mutations then operate on resolved paths and cannot be redirected through
    // a symlinked component — and it collapses every spelling of the filesystem
    // root (`/`, `//`, `/.`, a symlink to `/`, ...) to `/` so they can all be
    // rejected together.
    let resolved = resolve_appdir(&dir);
    if !resolved
        .components()
        .any(|component| matches!(component, Component::Normal(_)))
    {
        bail!(
            "brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root",
            dir.display()
        );
    }
    Ok(resolved)
}

/// Resolve `dir` by canonicalizing its longest existing ancestor and
/// re-appending the not-yet-existing tail. Symlinks in the existing portion are
/// followed, so the result is a real path the caller can safely use as a
/// containment boundary. Falls back to `dir` unchanged if nothing along the
/// path can be canonicalized (not expected for an absolute path, where `/`
/// always resolves).
pub(super) fn resolve_appdir(dir: &Path) -> PathBuf {
    for ancestor in dir.ancestors() {
        if let Ok(real) = ancestor.canonicalize() {
            let tail = dir.strip_prefix(ancestor).unwrap_or(Path::new(""));
            return real.join(tail);

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Set the env var to a real subdirectory, e.g. `export MISE_BREW_CASK_OPT_APPDIR=/Users/me/Applications`
  2. Unset the var so mise uses the default `/Applications`
  3. Remove or repoint any symlink intended as the appdir that resolves to `/`
  4. Fix the script that substitutes an empty/default value with `/`

Example fix

// before (shell)
export MISE_BREW_CASK_OPT_APPDIR=/
// after
export MISE_BREW_CASK_OPT_APPDIR="$HOME/Applications"
Defensive patterns

Strategy: validation

Validate before calling

fn appdir_env_ok(v: &str) -> bool {
    let p = std::path::Path::new(v);
    p.is_absolute() && p.components().any(|c| matches!(c, std::path::Component::Normal(_)))
}

Try / catch

match result {
    Err(e) if e.to_string().contains("must not resolve to the filesystem root") => {
        eprintln!("point the override at a real subdirectory, not '/'");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Setting APP_DIR_ENV to `/`, `//`, `/.`, a path consisting only of `.`/symlinks that resolve to `/` (e.g. a symlink named /rootlink pointing at `/`, value `/rootlink`), then invoking any code path that resolves the app dir.

Common situations: Placeholder value never properly configured; a script defaulting to `/` when a variable is empty; a symlink experiment pointing at the root; attempting to 'allow everything' by pointing the appdir at `/`.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


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