jdx/mise · error

brew-cask: app target

Error message

brew-cask: app target '{target_name}' must be an absolute path

What it means

brew-cask `app` artifact targets that contain a path separator (or $HOMEBREW_PREFIX/$APPDIR interpolation) must resolve to an absolute path so the install can verify they land inside the approved app directory or Homebrew prefix. A relative multi-segment target like 'subdir/Foo.app' cannot be safely bounded, so app_target_path rejects it at src/system/packages/brew/cask/paths.rs:146 instead of guessing where to place it.

Solutions

  1. Remove the '/' from the target or make it absolute: prefix it with '/' (or anchor it with $HOMEBREW_PREFIX or $APPDIR if the artifact belongs in the prefix or an appdir).
  2. If the target was meant to sit in the configured app dir, drop the directory components and keep only the bundle name (e.g. 'Foo.app'), which app_target_path joins onto the appdir automatically.
  3. If an absolute location under /Applications was intended but the leading slash was lost, restore it: 'Applications/Foo.app' -> '/Applications/Foo.app' or '$HOMEBREW_PREFIX/Applications/Foo.app'.
  4. For custom casks, fix the upstream cask source so the app target is either a bare bundle name or a valid absolute/interpolated path.

Example fix

// before (cask stanza)
app [["subdir/Foo.app"]]
// after
app [["Foo.app"]]
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_app_target(t: &str) -> bool {
    if t.contains('\0') { return false; }
    if !t.contains('/') { return true; }
    let expanded = t.replace("$HOMEBREW_PREFIX", "/opt/homebrew");
    Path::new(&expanded).is_absolute()
}

Type guard

fn valid_app_target(t: &str) -> Option<&str> {
    (!t.contains('\0') && (!t.contains('/') || t.starts_with('/') || t.starts_with("$HOMEBREW_PREFIX") || t.starts_with("$APPDIR")))
        .then_some(t)
}

Prevention

When it happens

Trigger: A cask's `app` stanza (or user-provided app target override) contains a '/' but the resulting path, after $HOMEBREW_PREFIX substitution, is relative — e.g. 'subdir/Foo.app' or './Foo.app'. Raised by app_target_path, called from installed_skip_reason, app_target_paths, install_one_with_ancestors, install_app, validate_adoptable_apps, and appdir_artifact_source.

Common situations: Hand-written or patched cask definitions where the app target was edited to a nested relative path; typos dropping the leading '/' from an absolute target like 'Applications/Foo.app'; tooling that strips a leading slash; custom cask registries with malformed app stanzas.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/b067796926e01f52. Report an issue: GitHub.

Appendix: source

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

            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> {
    let Ok(dir) = crate::env::var(APP_DIR_ENV) else {
        return Ok(PathBuf::from(DEFAULT_APP_DIR));
    };
    if dir.is_empty() {
        return Ok(PathBuf::from(DEFAULT_APP_DIR));

View on GitHub (pinned to 533346cc37)