{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/flight.rs","lineNumber":660,"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":642,"sourceCodeEnd":678,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/flight.rs#L642-L678","documentation":"A brew-cask structured copy step resolved its source to a directory, but the step was declared with recursive=false. The library refuses a shallow/ambiguous directory copy and requires the caller to opt in to recursive copying explicitly.","triggerScenarios":"FlightStep::Copy { source: <dir>, recursive: false } where source resolves to a directory inside staged_path — typically a glob matching a bundle/directory, or a literal path that became a directory after an upstream change.","commonSituations":"Cask authors copying a .app bundle or plugin directory but forgetting recursive=true; a glob that used to match a file now matches a directory after a version bump.","solutions":["Set recursive: true on the Copy flight step.","If only a single file is wanted, narrow the source path/glob to the specific file.","Reconsider using a Symlink step instead of copying a large directory."],"exampleFix":"// before\nFlightStep::Copy { source: \"MyApp.app\", recursive: false, .. }\n// after\nFlightStep::Copy { source: \"MyApp.app\", recursive: true, .. }","handlingStrategy":"validation","validationCode":"if source.is_dir() && !recursive {\n    return Err(eyre!(\"set recursive=true to copy directory {}\", source.display()));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default to recursive=true when copying .app bundles or plugin directories.","Check whether the resolved source is a file or directory before choosing recursive."],"tags":["brew-cask","validation","configuration"],"backgroundTag":"invalid-config-value","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}