{"record":{"id":"70653fe9062f4476","repo":"jdx/mise","slug":"brew-cask-structured-copy-source-must-resolve-to","errorCode":null,"errorMessage":"brew-cask: structured copy source must resolve to exactly one path","messagePattern":"brew-cask: structured copy source must resolve to exactly one path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":3437,"sourceCode":"            source,\n            target,\n            recursive,\n            overwrite,\n            source_glob,\n            guards,\n        } => {\n            if !guards\n                .iter()\n                .map(|guard| flight_guard_matches(cask, guard, staged_path, appdir))\n                .collect::<Result<Vec<_>>>()?\n                .into_iter()\n                .all(|matches| matches)\n            {\n                return Ok(());\n            }\n            let sources = flight_symlink_sources(cask, source, *source_glob, staged_path, appdir)?;\n            let [source] = sources.as_slice() else {\n                bail!(\"brew-cask: structured copy source must resolve to exactly one path\");\n            };\n            if !source.exists() {\n                bail!(\n                    \"brew-cask: structured copy source '{}' was not found\",\n                    source.display()\n                );\n            }\n            if source.is_dir() && !recursive {\n                bail!(\"brew-cask: structured directory copy requires recursive=true\");\n            }\n            let target = resolve_flight_path_with_context(cask, target, staged_path, appdir)?;\n            let external = !target.starts_with(staged_path);\n            let target_metadata = target.symlink_metadata().ok();\n            if target_metadata.is_some() {\n                if !overwrite {\n                    bail!(\n                        \"brew-cask: structured copy target '{}' already exists\",\n                        target.display()","sourceCodeStart":3419,"sourceCodeEnd":3455,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L3419-L3455","documentation":"mise's native Homebrew cask installer executes structured 'flight' (artifact) steps when installing a cask. A copy step resolves its source via flight_symlink_sources, which supports globs, but copy semantics allow exactly one source path. If the resolved list is empty or contains more than one entry, the install aborts before touching the target.","triggerScenarios":"A copy artifact whose source_glob=true pattern expands to zero or two-or-more paths under the staged caskroom directory, e.g. source 'payload/*.app' matching two .app bundles, or a brace pattern '{a,b}/**' where both alternatives exist.","commonSituations":"Homebrew cask API metadata whose glob was written against Homebrew's staging layout but mismatches mise's staged tree; a cask version bump renames payloads so a previously-single glob now matches two files; brace expansions where both branches are present in the archive.","solutions":["List the staged directory (e.g. \"$(brew --caskroom)/<token>/<version>\") and see what the source pattern actually matches","Tighten the copy source in the cask artifact data so it selects exactly one path, or drop source_glob and name the file literally","If several files must be handled, split into one copy step per file, or use a symlink step which supports multiple sources","If the upstream cask metadata itself is wrong, report it to homebrew-cask and pin the previous cask version until fixed"],"exampleFix":"// before (cask artifact data)\ncopy { source: \"payload/*.app\", source_glob: true, target: { base: \"appdir\", path: \"MyApp.app\" } }\n// after\ncopy { source: \"payload/MyApp.app\", source_glob: false, target: { base: \"appdir\", path: \"MyApp.app\" } }","handlingStrategy":"validation","validationCode":"STAGED=\"$(brew --caskroom)/<token>/<version>\"\nn=$(find \"$STAGED/payload\" -maxdepth 1 -name '*.app' 2>/dev/null | wc -l)\nif [ \"$n\" -ne 1 ]; then echo \"copy glob matches $n paths\"; fi","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Dry-run copy source globs against the staged directory before install and require exactly one match","Avoid brace expansions in copy sources unless only one alternative can ever exist","Review cask artifact data after payload version bumps that rename files"],"tags":["brew","cask","copy","glob","artifact","filesystem"],"backgroundTag":"ambiguous-glob-match","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}