{"record":{"id":"e06a65c6e6c02654","repo":"jdx/mise","slug":"brew-cask-structured-move-with-multiple-sources-r","errorCode":null,"errorMessage":"brew-cask: structured move with multiple sources requires a directory target","messagePattern":"brew-cask: structured move with multiple sources requires a directory target","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":3509,"sourceCode":"}\n\nfn execute_flight_step(\n    cask: &Cask,\n    step: &FlightStep,\n    staged_path: &Path,\n    appdir: &Path,\n    targets: &mut FlightTargetTransaction,\n) -> Result<()> {\n    match step {\n        FlightStep::Move {\n            source,\n            target,\n            source_glob,\n        } => {\n            let sources = flight_sources(staged_path, source, *source_glob)?;\n            let target = resolve_flight_path(staged_path, target)?;\n            if sources.len() > 1 && !target.is_dir() {\n                bail!(\n                    \"brew-cask: structured move with multiple sources requires a directory target\"\n                );\n            }\n            for source in sources {\n                let target = if target.is_dir() {\n                    target.join(source.file_name().ok_or_else(|| {\n                        eyre!(\n                            \"brew-cask: structured move source '{}' has no file name\",\n                            source.display()\n                        )\n                    })?)\n                } else {\n                    target.clone()\n                };\n                if let Some(parent) = target.parent()\n                    && !parent.as_os_str().is_empty()\n                {\n                    file::create_dir_all(parent)?;","sourceCodeStart":3491,"sourceCodeEnd":3527,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L3491-L3527","documentation":"Raised while executing a structured `move` artifact step of a Homebrew cask. When the step's source is a glob that expands to more than one file, every entry is moved into the target, which is only possible if the target already exists as a directory (Unix `mv a b dir/` semantics). If the resolved target is missing or is a regular file, the step aborts before any file is touched.","triggerScenarios":"A `move` step with `source_glob: true` whose pattern matches 2+ paths (e.g. two `*.app` bundles in the staged payload) while the target path does not exist or resolves to a file. Also fires when an earlier directory-creation step was skipped because its guard (e.g. `unless_exists`) failed on this machine.","commonSituations":"A cask starts shipping two apps where it previously shipped one, so the old single-file target is now invalid; the staged payload layout changed between cask versions; the step that creates the target directory is OS-guarded and the install runs on the other OS.","solutions":["Ensure the target directory exists before the move: add an earlier create/move step, or target a directory that already exists (e.g. the appdir)","Tighten the glob so it matches exactly one file, or split into one single-source move step per file","If the target is intentionally a file, remove `source_glob: true` and reference the exact file name","List the staged caskroom directory to confirm how many files the glob actually matches on this payload"],"exampleFix":"# cask artifact stanza - before\nmove(source: \"*.app\", target: \"#{appdir}/MyApp.app\", source_glob: true)\n# after: target is an existing directory that receives both bundles\nmove(source: \"*.app\", target: \"#{appdir}\", source_glob: true)","handlingStrategy":"validation","validationCode":"// Before running the flight step, verify multi-source moves have a directory target\nlet sources = expand_staged_glob(staged_path, &step.source.path)?;\nif sources.len() > 1 {\n    let target = resolve_flight_path(staged_path, &step.target)?;\n    if !target.is_dir() {\n        return Err(eyre!(\"target '{}' must be an existing directory for a multi-source move\", target.display()));\n    }\n}","typeGuard":null,"tryCatchPattern":"// These are untyped eyre bail!()s; classify on the message\nlet msg = err.to_string();\nif msg.contains(\"multiple sources requires a directory target\") {\n    // create the target directory or narrow the glob, then re-run the step\n} else {\n    return Err(err);\n}","preventionTips":["When authoring globbed move steps, always target a directory a previous step guarantees exists","Dry-run glob expansion against the staged payload before install","Keep one artifact per file whenever the target must be a file path"],"tags":["brew-cask","file-move","glob","install-artifact"],"backgroundTag":"move-target-must-be-directory","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}