{"record":{"id":"8ba20e758dc46fd8","repo":"jdx/mise","slug":"brew-cask-appdir-artifact-is-ambiguous","errorCode":null,"errorMessage":"brew-cask: APPDIR artifact '{}' is ambiguous: {}","messagePattern":"brew-cask: APPDIR artifact '(.+?)' is ambiguous: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":4278,"sourceCode":"    for app in apps {\n        let target = app_target_path(app.target_name())?;\n        let bundle = Path::new(bundle);\n        if !path_ends_with_ignore_ascii_case(Path::new(&app.source), bundle)\n            && !path_ends_with_ignore_ascii_case(&target, bundle)\n        {\n            continue;\n        }\n        let path = target.join(&suffix);\n        if path.is_file() {\n            matches.push(path);\n        }\n    }\n    matches.sort();\n    matches.dedup();\n    match matches.as_slice() {\n        [] => Ok(None),\n        [path] => Ok(Some(path.clone())),\n        _ => bail!(\n            \"brew-cask: APPDIR artifact '{}' is ambiguous: {}\",\n            source,\n            matches\n                .iter()\n                .map(|path| path.display().to_string())\n                .collect::<Vec<_>>()\n                .join(\", \")\n        ),\n    }\n}\n\nfn find_generated_completion_file(root: &Path, executable: &str) -> Result<Option<PathBuf>> {\n    let executable_path = Path::new(executable);\n    let direct = root.join(executable_path);\n    if direct.is_file() {\n        return Ok(Some(direct));\n    }\n    let matches = find_file_artifacts(root, executable_path);","sourceCodeStart":4260,"sourceCodeEnd":4296,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L4260-L4296","documentation":"A `$APPDIR/` artifact source matched more than one app bundle. `appdir_artifact_source` takes the first path component as a bundle name, ASCII case-insensitive suffix-matches it against every declared app artifact (both source and target names), keeps candidates where `target/suffix` is an existing file, and bails when several distinct matches remain because the artifact cannot be resolved unambiguously.","triggerScenarios":"A cask declaring multiple apps whose source or target names end with the same bundle name (e.g. `MyApp.app` and `Extras/MyApp.app`) while a `$APPDIR/MyApp.app/...` artifact matches both; case-insensitive matching collapsing two spellings of the same name.","commonSituations":"Casks bundling CLI and GUI apps with overlapping names; renamed app artifacts across versions leaving duplicates in the artifact list; target-name collisions between declared apps.","solutions":["Disambiguate the source: use the exact target name or a longer path that matches only one app","Rename or split app artifacts so each bundle resolves uniquely","Drop stale duplicate app entries from the cask definition after verifying which app is real"],"exampleFix":"# before - two apps end with MyApp.app, artifact matches both\napp(name: \"MyApp.app\", target: \"MyApp.app\")\napp(name: \"Extras/MyApp.app\", target: \"MyApp-Extras.app\")\ncompletion(source: \"$APPDIR/MyApp.app/Contents/Resources/_myapp\", ...)\n# after - reference the unique target name\ncompletion(source: \"$APPDIR/MyApp-Extras.app/Contents/Resources/_myapp\", ...)","handlingStrategy":"validation","validationCode":"// Pre-resolve appdir artifact sources and require a unique app match\nlet matches = appdir_artifact_source(source, &artifacts.apps)?;\nmatch matches {\n    None => /* skip or warn: source file absent in all bundles */,\n    Some(_) => /* safe to proceed */,\n}\n// appdir_artifact_source itself bails on ambiguity; enumerate apps first:\nlet bundle = Path::new(source.strip_prefix(\"$APPDIR/\")?).components().next()?;\nlet owners = artifacts.apps.iter().filter(|app| path_ends_with_ignore_ascii_case(Path::new(&app.source), &bundle)).count();\nif owners > 1 { return Err(eyre!(\"appdir source '{}' matches {} apps; disambiguate\", source, owners)); }","typeGuard":null,"tryCatchPattern":"if err.to_string().contains(\"is ambiguous\") {\n    // the message lists every matched path; pick the intended one and lengthen the source\n}","preventionTips":["Give every app artifact in a cask a unique resolvable bundle name","Reference completions by target name when source names overlap","Prune stale app entries when a cask renames or drops bundled apps"],"tags":["brew-cask","appdir","ambiguous-match","install-artifact"],"backgroundTag":"ambiguous-path-resolution","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}