{"record":{"id":"a831612bf0b32f97","repo":"jdx/mise","slug":"brew-cask-command-wrapper-args-and-env-require-ex","errorCode":null,"errorMessage":"brew-cask: command_wrapper args and env require executable","messagePattern":"brew-cask: command_wrapper args and env require executable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":5197,"sourceCode":"                .ok_or_else(|| eyre!(\"brew-cask: command_wrapper env must be an object\"))?\n                .iter()\n                .map(|(key, value)| {\n                    if !is_shell_env_name(key) {\n                        bail!(\"brew-cask: invalid command_wrapper environment name '{key}'\");\n                    }\n                    value\n                        .as_str()\n                        .map(|value| (key.clone(), value.to_string()))\n                        .ok_or_else(|| {\n                            eyre!(\"brew-cask: command_wrapper environment values must be strings\")\n                        })\n                })\n                .collect::<Result<BTreeMap<_, _>>>()\n        })\n        .transpose()?\n        .unwrap_or_default();\n    if content.is_some() && (!args.is_empty() || !env.is_empty()) {\n        bail!(\"brew-cask: command_wrapper args and env require executable\");\n    }\n    Ok(Some(CommandWrapperArtifact {\n        name: name.to_string(),\n        target: artifact_target(value, values),\n        content,\n        executable,\n        args,\n        env,\n    }))\n}\n\nfn parse_pkg_artifact(value: &Value) -> Result<Option<PkgArtifact>> {\n    let Some(pkg) = value.as_object().and_then(|o| o.get(\"pkg\")) else {\n        return Ok(None);\n    };\n    match pkg {\n        Value::String(source) => Ok(Some(PkgArtifact {\n            source: source.clone(),","sourceCodeStart":5179,"sourceCodeEnd":5215,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L5179-L5215","documentation":"Thrown by parse_command_wrapper_artifact in mise's brew-cask parser. A command_wrapper artifact options object must describe EITHER an inline script ('content') OR a wrapped 'executable'; 'args' and 'env' only make sense when an executable is being wrapped. Earlier checks (cask.rs:5151-5158) already guarantee content and executable are mutually exclusive, so reaching this branch means content was chosen AND args/env were also supplied, which has nothing to apply to.","triggerScenarios":"A cask artifact like {\"command_wrapper\": [\"tool\", {\"content\": \"#!/bin/sh ...\", \"args\": [\"--fast\"]}]} or with a non-empty \"env\" map. Fires during cask_artifacts() parsing when mise installs or upgrades that cask; any non-empty args array or env object alongside content triggers it.","commonSituations":"Hand-written or shim-converted cask JSON (cask_shim.rb) that copied args/env from an executable-style wrapper into a content-style wrapper; metadata generators that emit every option unconditionally; custom internal casks.","solutions":["Remove \"args\" and \"env\" from the content-based wrapper and bake those flags into the script text itself","Or switch to the executable form: {\"command_wrapper\": [\"tool\", {\"executable\": \"real-binary\", \"args\": [...], \"env\": {...}}]}","If the JSON comes from Homebrew's API and you cannot edit it, update mise to a newer build and report the cask token upstream"],"exampleFix":"// before\n{\"command_wrapper\": [\"kubectl\", {\"content\": \"#!/bin/sh\\nexec /target/wrapped kubectl \\\"$@\\\"\", \"args\": [\"--kubeconfig\", \"$HOME/.kube/config\"]}]}\n// after\n{\"command_wrapper\": [\"kubectl\", {\"content\": \"#!/bin/sh\\nexec /target/wrapped kubectl --kubeconfig \\\"$HOME/.kube/config\\\" \\\"$@\\\"\"}]}","handlingStrategy":"validation","validationCode":"fn command_wrapper_ok(v: &serde_json::Value) -> bool {\n    let Some(opts) = v.get(\"command_wrapper\").and_then(|w| w.get(1)).and_then(|o| o.as_object()) else { return true; };\n    let has_content = opts.get(\"content\").map(|c| c.as_str().is_some()).unwrap_or(false);\n    let args_empty = opts.get(\"args\").and_then(|a| a.as_array()).map(|a| a.is_empty()).unwrap_or(true);\n    let env_empty = opts.get(\"env\").and_then(|e| e.as_object()).map(|e| e.is_empty()).unwrap_or(true);\n    !has_content || (args_empty && env_empty)\n}","typeGuard":"fn is_command_wrapper_options(v: &serde_json::Value) -> bool {\n    v.as_object().map(|o| {\n        (o.contains_key(\"content\") ^ o.contains_key(\"executable\"))\n            && (o.get(\"args\").map(|a| a.is_array()).unwrap_or(true))\n            && (o.get(\"env\").map(|e| e.is_object()).unwrap_or(true))\n    }).unwrap_or(false)\n}","tryCatchPattern":"match cask_artifacts(&cask) {\n    Ok(artifacts) => { /* proceed */ }\n    Err(e) if e.to_string().contains(\"command_wrapper\") => {\n        eprintln!(\"cask {} has an invalid command_wrapper stanza; fix args/env vs content/executable: {e}\", cask.token);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Treat command_wrapper as two exclusive shapes: {content} OR {executable, args, env} — never mix","When generating cask JSON, emit args/env only when the executable key is present","Run cask JSON through the parser in CI before shipping custom casks"],"tags":["brew-cask","mise","command-wrapper","config-validation","mutually-exclusive-fields"],"backgroundTag":"brew-cask-stanza-validation","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}