{"record":{"id":"46519fba91a29027","repo":"jdx/mise","slug":"brew-cask-app-dir-env-must-be-an-absolute-46519f","errorCode":null,"errorMessage":"brew-cask: {APP_DIR_ENV} '{}' must be an absolute path","messagePattern":"brew-cask: (.+?) '(.+?)' must be an absolute path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/paths.rs","lineNumber":168,"sourceCode":"\n/// The directory `app` artifacts are linked into: `/Applications` unless\n/// [`APP_DIR_ENV`] overrides it.\n///\n/// The override is validated here rather than at the point of use because\n/// `app_target_path` treats the result as a containment boundary for symlinks\n/// that may be created with elevated privileges. An empty value falls back to\n/// the default so that exporting `MISE_BREW_CASK_OPT_APPDIR=` cannot disable\n/// that boundary: `Path::starts_with(\"\")` is true for every path.\npub(super) fn target_app_dir() -> Result<PathBuf> {\n    let Ok(dir) = crate::env::var(APP_DIR_ENV) else {\n        return Ok(PathBuf::from(DEFAULT_APP_DIR));\n    };\n    if dir.is_empty() {\n        return Ok(PathBuf::from(DEFAULT_APP_DIR));\n    }\n    let dir = PathBuf::from(dir);\n    if !dir.is_absolute() {\n        bail!(\n            \"brew-cask: {APP_DIR_ENV} '{}' must be an absolute path\",\n            dir.display()\n        );\n    }\n    if dir\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\n            \"brew-cask: {APP_DIR_ENV} '{}' must not contain '..'\",\n            dir.display()\n        );\n    }\n    // Resolve the override to a real absolute path: canonicalize its longest\n    // existing prefix and re-append the components that do not exist yet. This\n    // makes the appdir a symlink-free containment boundary — privileged cask\n    // mutations then operate on resolved paths and cannot be redirected through\n    // a symlinked component — and it collapses every spelling of the filesystem","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/paths.rs#L150-L186","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before (shell profile)\nexport MISE_BREW_CASK_OPT_APPDIR=apps\n// after\nexport MISE_BREW_CASK_OPT_APPDIR=\"$HOME/apps\"","handlingStrategy":"validation","validationCode":"function isValidAppdirOverride(v) {\n  if (v === undefined || v === \"\") return true; // unset/empty falls back to default\n  return typeof v === \"string\" && v.length > 0 && v.startsWith(\"/\");\n}","typeGuard":"function isAbsoluteAppdir(v) {\n  return typeof v === \"string\" && v.length > 0 && path.isAbsolute(v);\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["brew-cask","env-var","path-validation"],"backgroundTag":"invalid-env-var-value","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}