jdx/mise · error

brew-cask: command_wrapper args and env require executable

Error message

brew-cask: command_wrapper args and env require executable

What it means

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.

Source

Thrown at src/system/packages/brew/cask.rs:5197

                .ok_or_else(|| eyre!("brew-cask: command_wrapper env must be an object"))?
                .iter()
                .map(|(key, value)| {
                    if !is_shell_env_name(key) {
                        bail!("brew-cask: invalid command_wrapper environment name '{key}'");
                    }
                    value
                        .as_str()
                        .map(|value| (key.clone(), value.to_string()))
                        .ok_or_else(|| {
                            eyre!("brew-cask: command_wrapper environment values must be strings")
                        })
                })
                .collect::<Result<BTreeMap<_, _>>>()
        })
        .transpose()?
        .unwrap_or_default();
    if content.is_some() && (!args.is_empty() || !env.is_empty()) {
        bail!("brew-cask: command_wrapper args and env require executable");
    }
    Ok(Some(CommandWrapperArtifact {
        name: name.to_string(),
        target: artifact_target(value, values),
        content,
        executable,
        args,
        env,
    }))
}

fn parse_pkg_artifact(value: &Value) -> Result<Option<PkgArtifact>> {
    let Some(pkg) = value.as_object().and_then(|o| o.get("pkg")) else {
        return Ok(None);
    };
    match pkg {
        Value::String(source) => Ok(Some(PkgArtifact {
            source: source.clone(),

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Remove "args" and "env" from the content-based wrapper and bake those flags into the script text itself
  2. Or switch to the executable form: {"command_wrapper": ["tool", {"executable": "real-binary", "args": [...], "env": {...}}]}
  3. 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

Example fix

// before
{"command_wrapper": ["kubectl", {"content": "#!/bin/sh\nexec /target/wrapped kubectl \"$@\"", "args": ["--kubeconfig", "$HOME/.kube/config"]}]}
// after
{"command_wrapper": ["kubectl", {"content": "#!/bin/sh\nexec /target/wrapped kubectl --kubeconfig \"$HOME/.kube/config\" \"$@\""}]}
Defensive patterns

Strategy: validation

Validate before calling

fn command_wrapper_ok(v: &serde_json::Value) -> bool {
    let Some(opts) = v.get("command_wrapper").and_then(|w| w.get(1)).and_then(|o| o.as_object()) else { return true; };
    let has_content = opts.get("content").map(|c| c.as_str().is_some()).unwrap_or(false);
    let args_empty = opts.get("args").and_then(|a| a.as_array()).map(|a| a.is_empty()).unwrap_or(true);
    let env_empty = opts.get("env").and_then(|e| e.as_object()).map(|e| e.is_empty()).unwrap_or(true);
    !has_content || (args_empty && env_empty)
}

Type guard

fn is_command_wrapper_options(v: &serde_json::Value) -> bool {
    v.as_object().map(|o| {
        (o.contains_key("content") ^ o.contains_key("executable"))
            && (o.get("args").map(|a| a.is_array()).unwrap_or(true))
            && (o.get("env").map(|e| e.is_object()).unwrap_or(true))
    }).unwrap_or(false)
}

Try / catch

match cask_artifacts(&cask) {
    Ok(artifacts) => { /* proceed */ }
    Err(e) if e.to_string().contains("command_wrapper") => {
        eprintln!("cask {} has an invalid command_wrapper stanza; fix args/env vs content/executable: {e}", cask.token);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/a831612bf0b32f97. Report an issue: GitHub.