jdx/mise · error

brew-cask: command_wrapper '{}' must set exactly one of cont

Error message

brew-cask: command_wrapper '{}' must set exactly one of content or executable

What it means

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.

Source

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

                .env
                .iter()
                .map(|(key, value)| {
                    let value = expand_command_wrapper_value(value, appdir, cask);
                    Ok(format!(
                        "{key}={}",
                        shell_escape::unix::escape(Cow::Owned(value))
                    ))
                })
                .collect::<Result<Vec<_>>>()?;
            let mut command = Vec::new();
            command.extend(env);
            command.push("exec".to_string());
            command.push(shell_escape::unix::escape(Cow::Owned(executable)).into_owned());
            command.extend(args);
            command.push("\"$@\"".to_string());
            format!("#!/bin/bash\n{}\n", command.join(" "))
        }
        _ => bail!(
            "brew-cask: command_wrapper '{}' must set exactly one of content or executable",
            wrapper.name
        ),
    };
    file::write(&target, content)?;
    file::make_executable(&target)?;
    Ok(())
}

fn expand_command_wrapper_content(value: &str, appdir: &Path) -> String {
    value
        .replace("$HOMEBREW_PREFIX", &prefix::prefix().to_string_lossy())
        .replace("$APPDIR", &appdir.to_string_lossy())
}

fn expand_command_wrapper_value(value: &str, appdir: &Path, cask: &Cask) -> String {
    let staged_path = caskroom_version_dir(&cask.token, &cask.version);
    expand_cask_template(value, &staged_path, appdir, Some(&cask.version))

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Fix the wrapper definition so exactly one of 'content' or 'executable' is present (this mirrors the parse-time checks that produce errors 757/758).
  2. Clear the cached cask metadata and re-fetch, so the parse-time xor validation runs on fresh data.
  3. If building artifacts in code, centralize construction through the parser instead of hand-assembling the struct.

Example fix

# before
command_wrapper: [["tool", {"content": "#!/bin/sh\n...", "executable": "bin/tool"}]]

# after — keep only one
command_wrapper: [["tool", {"executable": "bin/tool"}]]
Defensive patterns

Strategy: validation

Validate before calling

let xor = wrapper.content.is_some() ^ wrapper.executable.is_some();
assert!(xor, "command_wrapper '{}' needs exactly one of content|executable", wrapper.name);

Prevention

When it happens

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

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

Related errors


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