{"record":{"id":"837fd4c1bb44c3db","repo":"jdx/mise","slug":"brew-cask-structured-directory-copy-requires-recu-837fd4","errorCode":null,"errorMessage":"brew-cask: structured directory copy requires recursive=true","messagePattern":"brew-cask: structured directory copy requires recursive=true","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/flight.rs","lineNumber":697,"sourceCode":"            overwrite,\n            source_glob,\n            guards,\n        } => {\n            if !flight_guards_pass(cask, guards, staged_path, appdir)? {\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()\n                    );\n                }\n                if external {\n                    targets.protect(&target)?;\n                } else {\n                    file::remove_all(&target)?;\n                }\n            }\n            if let Some(parent) = target.parent() {","sourceCodeStart":679,"sourceCodeEnd":715,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/flight.rs#L679-L715","documentation":"A structured copy step whose source is a directory requires `recursive: true`; without it the step would silently copy an empty directory entry, so it is rejected. This mirrors `cp` semantics where copying a directory needs `-r`.","triggerScenarios":"`execute_flight_step` Copy step where `source.is_dir()` is true but the step's `recursive` flag is false/absent in the cask stanza.","commonSituations":"A cask stanza copies a framework or app bundle directory but omits `recursive = true`; a stanza written for a single file was repointed at a directory after an upstream restructure.","solutions":["Add `recursive = true` to the copy step in the cask definition","If only a single file was intended, correct the source path to the file rather than its directory","Verify with `ls` whether the staged source is a directory before choosing flags"],"exampleFix":"// before\n{ copy: { source: \"App.app\", target: \"/Applications/App.app\" } }\n// after\n{ copy: { source: \"App.app\", target: \"/Applications/App.app\", recursive: true } }","handlingStrategy":"validation","validationCode":"let src = staged.join(source);\nif src.is_dir() && !recursive {\n    return Err(\"directory source requires recursive=true\");\n}","typeGuard":"fn copy_flags_ok(src: &Path, recursive: bool) -> bool { !src.is_dir() || recursive }","tryCatchPattern":null,"preventionTips":["Always set recursive=true when copying bundles (.app, .framework)","Lint cask copy stanzas: directory source implies recursive","Check source type with `test -d` when authoring stanzas"],"tags":["brew-cask","flight-step","copy","recursive"],"backgroundTag":"missing-required-flag","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"}