jdx/mise · error

brew-cask: command_wrapper

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.

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.

Appendix: 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)