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
- 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.
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
- 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.
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
- brew-cask: command_wrapper args and env require executable
- brew-cask: command wrapper '{}' was not staged
- brew-cask: command_wrapper requires a command name without p
- brew-cask: command_wrapper has unsupported option {}
- brew-cask: command_wrapper requires content or executable
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/e5aba1273aadd2b4.
Report an issue: GitHub.