{"record":{"id":"e5aba1273aadd2b4","repo":"jdx/mise","slug":"brew-cask-command-wrapper-must-set-exactly-o","errorCode":null,"errorMessage":"brew-cask: command_wrapper '{}' must set exactly one of content or executable","messagePattern":"brew-cask: command_wrapper '(.+?)' must set exactly one of content or executable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":4657,"sourceCode":"                .env\n                .iter()\n                .map(|(key, value)| {\n                    let value = expand_command_wrapper_value(value, appdir, cask);\n                    Ok(format!(\n                        \"{key}={}\",\n                        shell_escape::unix::escape(Cow::Owned(value))\n                    ))\n                })\n                .collect::<Result<Vec<_>>>()?;\n            let mut command = Vec::new();\n            command.extend(env);\n            command.push(\"exec\".to_string());\n            command.push(shell_escape::unix::escape(Cow::Owned(executable)).into_owned());\n            command.extend(args);\n            command.push(\"\\\"$@\\\"\".to_string());\n            format!(\"#!/bin/bash\\n{}\\n\", command.join(\" \"))\n        }\n        _ => bail!(\n            \"brew-cask: command_wrapper '{}' must set exactly one of content or executable\",\n            wrapper.name\n        ),\n    };\n    file::write(&target, content)?;\n    file::make_executable(&target)?;\n    Ok(())\n}\n\nfn expand_command_wrapper_content(value: &str, appdir: &Path) -> String {\n    value\n        .replace(\"$HOMEBREW_PREFIX\", &prefix::prefix().to_string_lossy())\n        .replace(\"$APPDIR\", &appdir.to_string_lossy())\n}\n\nfn expand_command_wrapper_value(value: &str, appdir: &Path, cask: &Cask) -> String {\n    let staged_path = caskroom_version_dir(&cask.token, &cask.version);\n    expand_cask_template(value, &staged_path, appdir, Some(&cask.version))","sourceCodeStart":4639,"sourceCodeEnd":4675,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L4639-L4675","documentation":"When staging a command_wrapper artifact into the caskroom, mise builds the wrapper either from literal 'content' or by generating a bash exec wrapper around 'executable'. Exactly one of the two must be set; if neither or both are present this bail fires at write time (src/system/packages/brew/cask.rs:4657). Parse-time validation normally rejects this earlier, so seeing it means an artifact reached the writer in an inconsistent state.","triggerScenarios":"A CommandWrapperArtifact constructed with both content and executable, or with neither — typically from code that builds artifacts directly rather than via parse_command_wrapper, stale cached cask metadata parsed by an older/newer mise, or a partially patched metadata file.","commonSituations":"Mixed mise versions sharing a metadata cache; local development against mise's brew-cask API where the artifact struct was hand-built; upstream metadata that added new wrapper fields mid-schema-change.","solutions":["Fix the wrapper definition so exactly one of 'content' or 'executable' is present (this mirrors the parse-time checks that produce errors 757/758).","Clear the cached cask metadata and re-fetch, so the parse-time xor validation runs on fresh data.","If building artifacts in code, centralize construction through the parser instead of hand-assembling the struct."],"exampleFix":"# before\ncommand_wrapper: [[\"tool\", {\"content\": \"#!/bin/sh\\n...\", \"executable\": \"bin/tool\"}]]\n\n# after — keep only one\ncommand_wrapper: [[\"tool\", {\"executable\": \"bin/tool\"}]]","handlingStrategy":"validation","validationCode":"let xor = wrapper.content.is_some() ^ wrapper.executable.is_some();\nassert!(xor, \"command_wrapper '{}' needs exactly one of content|executable\", wrapper.name);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Enforce the content/executable xor in your own artifact builders, not just at parse time.","Clear cached cask metadata when switching mise versions.","Construct artifacts via the parser rather than hand-building structs."],"tags":["brew-cask","command-wrapper","config-validation","mise"],"backgroundTag":"mutually-exclusive-config-fields","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}