{"record":{"id":"1d76d9d88054aa35","repo":"jdx/mise","slug":"brew-cask-app-dir-env-must-be-an-absolute","errorCode":null,"errorMessage":"brew-cask: {APP_DIR_ENV} '{}' must be an absolute path","messagePattern":"brew-cask: (.+?) '(.+?)' must be an absolute path","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":6033,"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.\nfn 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":6015,"sourceCodeEnd":6051,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L6015-L6051","documentation":"target_app_dir reads MISE_BREW_CASK_OPT_APPDIR (see APP_DIR_ENV, src/system/packages/brew/cask.rs:41) to override the default /Applications app dir. An empty value deliberately falls back to the default, but a non-empty value that is not absolute is rejected. The app dir is the containment boundary for privileged cask linking, so a relative value would make the boundary machine-dependent and unsafe.","triggerScenarios":"Exporting MISE_BREW_CASK_OPT_APPDIR with a relative value such as \"Applications\" or a quoted \"~/Applications\" (tilde not expanded by the shell).","commonSituations":"Shell config (.zshrc/.bashrc) exporting the var with ~ inside quotes or a shorthand relative path; CI scripts setting the var from a relative working-dir path.","solutions":["Export an absolute path: `export MISE_BREW_CASK_OPT_APPDIR=\"$HOME/Applications\"`","Or unset the variable to use the default /Applications","Check for stray exports: `env | grep MISE_BREW_CASK_OPT_APPDIR`","After fixing, re-run the mise cask install/upgrade"],"exampleFix":"# before\ngrep MISE_BREW_CASK_OPT_APPDIR ~/.zshrc\nexport MISE_BREW_CASK_OPT_APPDIR=~/Applications\n# after\nexport MISE_BREW_CASK_OPT_APPDIR=\"$HOME/Applications\"","handlingStrategy":"validation","validationCode":"fn appdir_env_valid(val: Option<&str>) -> bool {\n    match val {\n        None | Some(\"\") => true, // empty falls back to default\n        Some(dir) => std::path::Path::new(dir).is_absolute(),\n    }\n}\n// before install:\nif !appdir_env_valid(std::env::var(\"MISE_BREW_CASK_OPT_APPDIR\").ok().as_deref()) {\n    eprintln!(\"MISE_BREW_CASK_OPT_APPDIR must be an absolute path\");\n}","typeGuard":null,"tryCatchPattern":"match target_app_dir() {\n    Ok(dir) => dir,\n    Err(e) if e.to_string().contains(\"must be an absolute path\") => {\n        std::env::remove_var(\"MISE_BREW_CASK_OPT_APPDIR\");\n        target_app_dir() // retry with the default /Applications\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Always export MISE_BREW_CASK_OPT_APPDIR as an absolute, tilde-expanded path: export MISE_BREW_CASK_OPT_APPDIR=\"$HOME/Applications\"","Add a shell-config sanity check: case \"$MISE_BREW_CASK_OPT_APPDIR\" in /*) ;; \"\"|*) echo \"appdir must be absolute\" >&2;; esac","Leave the variable unset to use /Applications"],"tags":["brew-cask","env-var","appdir","configuration"],"backgroundTag":"invalid-env-var-value","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}