jdx/mise · error
brew-cask: ' ' must be an absolute path
Error message
brew-cask: {APP_DIR_ENV} '{}' must be an absolute path What it means
When the MISE_BREW_CASK_OPT_APPDIR environment variable (APP_DIR_ENV) is set to redirect where cask `app` bundles are installed, it must be an absolute path. target_app_dir rejects relative values because the appdir is used as a containment boundary for privileged symlink operations, and a relative boundary would be meaningless (src/system/packages/brew/cask/paths.rs:168).
Solutions
- Set MISE_BREW_CASK_OPT_APPDIR to an absolute path, e.g. export MISE_BREW_CASK_OPT_APPDIR=/Users/you/Applications or /opt/apps.
- If you meant the default, unset the variable entirely (or set it empty — an empty value falls back to /Applications).
- Expand '~' or relative references in your shell profile before exporting: use "$HOME/apps" instead of "~/apps" or a bare relative path.
- Re-run the mise cask command after fixing the value; the error recurs on every cask operation while the bad value is exported.
Example fix
// before (shell profile) export MISE_BREW_CASK_OPT_APPDIR=apps // after export MISE_BREW_CASK_OPT_APPDIR="$HOME/apps"
Defensive patterns
Strategy: validation
Validate before calling
function isValidAppdirOverride(v) {
if (v === undefined || v === "") return true; // unset/empty falls back to default
return typeof v === "string" && v.length > 0 && v.startsWith("/");
} Type guard
function isAbsoluteAppdir(v) {
return typeof v === "string" && v.length > 0 && path.isAbsolute(v);
} Prevention
- Always export MISE_BREW_CASK_OPT_APPDIR as a clean absolute path starting with '/'.
- Expand '~' and variables in your shell profile before exporting (use "$HOME/..." not "~/...").
- Set the variable in one place and verify it with a startup check (realpath + is-absolute assertion).
- Unset the variable entirely if you want the default /Applications behavior.
When it happens
Trigger: Setting MISE_BREW_CASK_OPT_APPDIR to a relative path such as 'apps' or './Applications' and then performing any cask install, adopt validation, or target resolution that calls target_app_dir (directly or via app_target_path, cask_appdir, target_path, or allowed_appdir_roots).
Common situations: Users exporting the override with a value intended to be relative to HOME or the project; copy-pasted config where the leading '/' was lost; shell profiles that set the var unconditionally with a bad value; unexpanded '~' values which are not absolute.
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
- brew-cask: ' ' must not resolve to the filesystem root
- brew-cask: ' ' must not contain '..
- brew-cask: app target contains NUL
- brew-cask: app target
- brew-cask: app target
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/46519fba91a29027.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask/paths.rs:168
/// 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));
}
let dir = PathBuf::from(dir);
if !dir.is_absolute() {
bail!(
"brew-cask: {APP_DIR_ENV} '{}' must be an absolute path",
dir.display()
);
}
if dir
.components()
.any(|component| matches!(component, Component::ParentDir))
{
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 filesystemView on GitHub (pinned to 533346cc37)