{"record":{"id":"3ab367f5821b2a04","repo":"jdx/mise","slug":"brew-cask-command-wrapper-requires-a-command-name","errorCode":null,"errorMessage":"brew-cask: command_wrapper requires a command name without path components","messagePattern":"brew-cask: command_wrapper requires a command name without path components","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":5125,"sourceCode":"    }\n}\n\nfn parse_command_wrapper_artifact(value: &Value) -> Result<Option<CommandWrapperArtifact>> {\n    let Some(wrapper) = value.as_object().and_then(|o| o.get(\"command_wrapper\")) else {\n        return Ok(None);\n    };\n    let values = wrapper\n        .as_array()\n        .ok_or_else(|| eyre!(\"brew-cask: command_wrapper metadata must be an array\"))?;\n    let name = values\n        .first()\n        .and_then(Value::as_str)\n        .ok_or_else(|| eyre!(\"brew-cask: command_wrapper requires a command name\"))?;\n    let name_path = Path::new(name);\n    if name_path.file_name().and_then(|name| name.to_str()) != Some(name)\n        || matches!(name, \".\" | \"..\")\n    {\n        bail!(\"brew-cask: command_wrapper requires a command name without path components\");\n    }\n    let options = values\n        .get(1)\n        .and_then(Value::as_object)\n        .ok_or_else(|| eyre!(\"brew-cask: command_wrapper requires options\"))?;\n    let mut unsupported = options\n        .keys()\n        .filter(|key| !matches!(key.as_str(), \"content\" | \"executable\" | \"args\" | \"env\"))\n        .cloned()\n        .collect::<Vec<_>>();\n    unsupported.sort();\n    if !unsupported.is_empty() {\n        bail!(\n            \"brew-cask: command_wrapper has unsupported option {}\",\n            unsupported.join(\", \")\n        );\n    }\n    let content = options","sourceCodeStart":5107,"sourceCodeEnd":5143,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L5107-L5143","documentation":"A command_wrapper artifact is a two-element array: [name, options]. mise validates the name is a single path component — Path::file_name() of it must equal the whole string and it must not be '.' or '..' — because the name becomes both a filename in the caskroom and a command in the bin directory. Anything containing '/', '.', or '..' is rejected up front.","triggerScenarios":"Metadata where the wrapper name is 'bin/tool', 'foo/bar', './tool', '.', or '..' — the check at src/system/packages/brew/cask.rs:5125 compares file_name() back to the full string and explicitly bans the two special components.","commonSituations":"Authors pasting a path into the name slot instead of the executable option; metadata generators emitting $APPDIR-prefixed names; attempts to make the wrapper land in a subdirectory of bin (unsupported).","solutions":["Use a plain command name: 'tool', not 'bin/tool'.","If the wrapper must exec a specific path, put the path in the 'executable' option and keep the name bare: [\"tool\", {\"executable\": \"bin/tool\"}].","If you need namespaced commands, create separate wrappers with distinct plain names."],"exampleFix":"# before\ncommand_wrapper: [[\"bin/tool\", {\"executable\": \"MyApp.app/Contents/MacOS/tool\"}]]\n\n# after\ncommand_wrapper: [[\"tool\", {\"executable\": \"MyApp.app/Contents/MacOS/tool\"}]]","handlingStrategy":"type-guard","validationCode":"fn is_plain_command_name(name: &str) -> bool {\n    !name.is_empty()\n        && std::path::Path::new(name).file_name().and_then(|n| n.to_str()) == Some(name)\n        && !matches!(name, \".\" | \"..\")\n}\n// assert is_plain_command_name(&wrapper_name) before building the artifact","typeGuard":"function isPlainCommandName(name) {\n  return /^[^/]+$/.test(name) && name !== \".\" && name !== \"..\" && name.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Keep wrapper names single path components; put real paths in the 'executable' option.","Regex-lint names in metadata: /^[^/]+$/ plus not '.'/'..'.","Generate wrappers from a template that takes name and executable as separate fields."],"tags":["brew-cask","command-wrapper","name-validation","mise"],"backgroundTag":"invalid-command-name","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}