{"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":3563,"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":3545,"sourceCodeEnd":3581,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L3545-L3581","documentation":"A structured `copy` artifact step requires its source to resolve to exactly one path. The step resolves sources through `flight_symlink_sources` (honoring `source_glob` and appdir context), then destructures with `let [source] = sources`; zero matches or multiple matches both fail this check and abort the cask install before any file is copied.","triggerScenarios":"`copy` with `source_glob: true` where the staged glob matches zero or 2+ paths; a source string whose template expansion resolves to several staged locations. All `guards` must have matched first, so this fires on the normal path of a mis-scoped glob.","commonSituations":"Upstream payload gains or loses a file so a previously exact glob becomes ambiguous or empty; overly broad patterns like `**/*.app`; version-stamped file names changing between releases.","solutions":["Pin the glob to the exact file name so it matches exactly one path","Split into one copy step per file when several files must be copied","If the glob matched zero, inspect the staged payload and re-stage (fresh download) to rule out partial extraction","Guard genuinely optional files with `if_exists` so the step skips instead of failing"],"exampleFix":"# before\ncopy(source: \"payload/**/*\", target: \"#{staged}/out\", source_glob: true)\n# after - exactly one concrete source per step\ncopy(source: \"payload/MyApp.app\", target: \"#{staged}/out/MyApp.app\")","handlingStrategy":"validation","validationCode":"// Pre-flight: a copy step must resolve to exactly one source\nlet sources = flight_symlink_sources(cask, &step.source, step.source_glob, staged_path, appdir)?;\nmatch sources.as_slice() {\n    [single] => Ok(single.clone()),\n    [] => Err(eyre!(\"copy source '{}' matched nothing; check payload layout\", step.source.path)),\n    many => Err(eyre!(\"copy source '{}' matched {} paths; split into per-file steps\", step.source.path, many.len())),\n}","typeGuard":null,"tryCatchPattern":"if err.to_string().contains(\"copy source must resolve to exactly one path\") {\n    // report the staged directory listing next to the error to speed up diagnosis\n}","preventionTips":["Prefer concrete single-file sources over globs in copy steps","Test artifact stanzas against a freshly staged payload after every upstream release","Guard optional copies with `if_exists` so empty matches skip instead of fail"],"tags":["brew-cask","file-copy","glob","install-artifact"],"backgroundTag":"glob-multiple-matches","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}