{"record":{"id":"b067796926e01f52","repo":"jdx/mise","slug":"brew-cask-app-target-target-name-must-be-an-a-b06779","errorCode":null,"errorMessage":"brew-cask: app target '{target_name}' must be an absolute path","messagePattern":"brew-cask: app target '(.+?)' must be an absolute path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/paths.rs","lineNumber":146,"sourceCode":"            if path.starts_with(&app_dir) || path.starts_with(&prefix_app_dir) {\n                return Ok(path);\n            }\n            // Casks routinely hardcode an absolute `/Applications/Foo.app`\n            // target. When an override appdir is configured, relocate such a\n            // target into it (preserving any subdirectories) rather than\n            // rejecting it. `$HOMEBREW_PREFIX`-anchored targets are handled by\n            // the check above and are never relocated.\n            if app_dir != Path::new(DEFAULT_APP_DIR)\n                && let Ok(rest) = path.strip_prefix(DEFAULT_APP_DIR)\n            {\n                return Ok(app_dir.join(rest));\n            }\n            bail!(\n                \"brew-cask: app target '{target_name}' must be under {}\",\n                app_dir.display()\n            );\n        }\n        bail!(\"brew-cask: app target '{target_name}' must be an absolute path\");\n    }\n    Ok(app_dir.join(target_name))\n}\n\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));","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/paths.rs#L128-L164","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","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.","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'.","For custom casks, fix the upstream cask source so the app target is either a bare bundle name or a valid absolute/interpolated path."],"exampleFix":"// before (cask stanza)\napp [[\"subdir/Foo.app\"]]\n// after\napp [[\"Foo.app\"]]","handlingStrategy":"validation","validationCode":"fn is_valid_app_target(t: &str) -> bool {\n    if t.contains('\\0') { return false; }\n    if !t.contains('/') { return true; }\n    let expanded = t.replace(\"$HOMEBREW_PREFIX\", \"/opt/homebrew\");\n    Path::new(&expanded).is_absolute()\n}","typeGuard":"fn valid_app_target(t: &str) -> Option<&str> {\n    (!t.contains('\\0') && (!t.contains('/') || t.starts_with('/') || t.starts_with(\"$HOMEBREW_PREFIX\") || t.starts_with(\"$APPDIR\")))\n        .then_some(t)\n}","tryCatchPattern":null,"preventionTips":["Keep app targets as bare bundle names (\"Foo.app\") unless a specific absolute location is required.","Never hand-edit a cask target to a nested relative path; use the appdir override to relocate installs instead.","Lint custom casks: every slash-containing app target must start with '/' or $HOMEBREW_PREFIX/$APPDIR.","Test cask stanzas with a dry-run/validate mode before a real install."],"tags":["brew-cask","path-validation","config"],"backgroundTag":"invalid-argument-format","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"}