jdx/mise · error

brew-cask: app target '{target_name}' must be under {}

Error message

brew-cask: app target '{target_name}' must be under {}

What it means

`app_target_path` accepts absolute app targets only when they fall under the configured app dir (`/Applications` by default, or the APP_DIR override) or the Homebrew prefix's Applications dir. An absolute target outside those roots is rejected so cask installs cannot place privileged symlinks in arbitrary locations. With an override appdir configured, hardcoded `/Applications/...` targets are relocated instead of rejected; anything else fails with this error.

Source

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

        {
            bail!("brew-cask: app target '{target_name}' must not contain '..'");
        }
        if path.is_absolute() {
            let prefix_app_dir = prefix::prefix().join("Applications");
            if path.starts_with(&app_dir) || path.starts_with(&prefix_app_dir) {
                return Ok(path);
            }
            // Casks routinely hardcode an absolute `/Applications/Foo.app`
            // target. When an override appdir is configured, relocate such a
            // target into it (preserving any subdirectories) rather than
            // rejecting it. `$HOMEBREW_PREFIX`-anchored targets are handled by
            // the check above and are never relocated.
            if app_dir != Path::new(DEFAULT_APP_DIR)
                && let Ok(rest) = path.strip_prefix(DEFAULT_APP_DIR)
            {
                return Ok(app_dir.join(rest));
            }
            bail!(
                "brew-cask: app target '{target_name}' must be under {}",
                app_dir.display()
            );
        }
        bail!("brew-cask: app target '{target_name}' must be an absolute path");
    }
    Ok(app_dir.join(target_name))
}

/// The directory `app` artifacts are linked into: `/Applications` unless
/// [`APP_DIR_ENV`] overrides it.
///
/// The override is validated here rather than at the point of use because
/// `app_target_path` treats the result as a containment boundary for symlinks
/// that may be created with elevated privileges. An empty value falls back to
/// the default so that exporting `MISE_BREW_CASK_OPT_APPDIR=` cannot disable
/// that boundary: `Path::starts_with("")` is true for every path.
pub(super) fn target_app_dir() -> Result<PathBuf> {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Change the cask target to a bare app-bundle name (`Foo.app`) so it is placed under the app dir automatically
  2. Make the target absolute under the allowed app dir, e.g. `/Applications/Foo.app` or `$HOMEBREW_PREFIX/Applications/Foo.app`
  3. If you use an APP_DIR override, set the target relative (no leading `/`) so it is joined to the override dir
  4. Check the APP_DIR env var value — an unusual override shrinks the set of accepted absolute targets

Example fix

// before (cask stanza)
app target: '/System/Library/Foo.app'
// after
app target: 'Foo.app'
Defensive patterns

Strategy: validation

Validate before calling

fn absolute_target_in_allowed_roots(target: &str, app_dir: &std::path::Path) -> bool {
    let p = std::path::PathBuf::from(target.replace("$HOMEBREW_PREFIX", "/opt/homebrew"));
    p.is_absolute()
        && (p.starts_with(app_dir) || p.starts_with("/opt/homebrew/Applications")
            || p.starts_with("/Applications"))
}

Try / catch

match result {
    Err(e) if e.to_string().contains("must be under") && e.to_string().contains("app target") => {
        eprintln!("move the target under the app dir or use a bare name");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling install/validation via `app_target_path` with a cask `app target:` containing an absolute path that starts outside the app dir and the Homebrew prefix Applications dir, e.g. `target: '/System/Foo.app'` while no relocation rule applies.

Common situations: Casks hardcoding unusual absolute destinations; a custom MISE_BREW_CASK_OPT_APPDIR pointing somewhere the target doesn't fall under; a cask written for a different layout; typos in the target path.

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/2e47dddb532f173c. Report an issue: GitHub.