jdx/mise · error

expected `run` to be a string or array of strings

Error message

expected `run` to be a string or array of strings

What it means

When a bootstrap hook is written in table form, the table's run key must be a string or an array of strings. This error fires when run exists but is a different TOML type — integer, float, boolean, inline table, etc. mise hooks accept only commands, not structured options objects.

Source

Thrown at src/system/hooks.rs:89

    pub fn from_toml(phase_raw: &str, value: toml::Value) -> Result<Vec<Self>> {
        let Some(phase) = BootstrapHookPhase::parse(phase_raw) else {
            let valid = BootstrapHookPhase::iter()
                .map(|phase| phase.as_str())
                .collect::<Vec<_>>();
            bail!(
                "unknown bootstrap hook phase {phase_raw:?}; valid phases are: {}",
                valid.join(", ")
            );
        };
        let runs = match value {
            toml::Value::String(run) => vec![run],
            toml::Value::Array(values) => string_array(values, "expected string commands")?,
            toml::Value::Table(mut table) => match table.remove("run") {
                Some(toml::Value::String(run)) => vec![run],
                Some(toml::Value::Array(values)) => {
                    string_array(values, "expected `run` to contain string commands")?
                }
                Some(_) => bail!("expected `run` to be a string or array of strings"),
                None => bail!("expected a `run` command"),
            },
            _ => bail!("expected a string, array of strings, or table with `run`"),
        };
        let hooks = runs
            .into_iter()
            .filter_map(|run| {
                let run = run.trim().to_string();
                if run.is_empty() {
                    warn!("[bootstrap.hooks.{phase}]: empty command, ignoring entry");
                    None
                } else {
                    Some(Self { phase, run })
                }
            })
            .collect();
        Ok(hooks)
    }

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Set run to a plain command string: run = "./cleanup.sh"
  2. Or an array of command strings: run = ["step1.sh", "step2.sh"]
  3. Remove structured options from run; mise hooks only take command strings

Example fix

# before
[bootstrap.hooks.final]
run = { cmd = "./cleanup.sh" }

# after
[bootstrap.hooks.final]
run = "./cleanup.sh"
Defensive patterns

Strategy: validation

Validate before calling

# TOML tables for hooks must use run as string or array of strings
# quick check: any run key that is not quoted or a bracketed list of quoted strings
awk '/^\[bootstrap\.hooks\./ {inhook=1} /^\[/ && !/bootstrap\.hooks/ {inhook=0} inhook && /^run[ \t]*=/ && $0 !~ /run[ \t]*=[ \t]*("|\[)/ {print "bad run value: " $0}' mise.toml

Prevention

When it happens

Trigger: Writing [bootstrap.hooks.final] with run = 3, run = true, or run = { cmd = "./x.sh" } — a run key whose TOML type is neither string nor array.

Common situations: Porting configs from tools whose hooks take an options object (cmd/args/shell style); pasting booleans or numbers as placeholders; assuming any value is coerced to a string.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/fcb1c19cef04657d. Report an issue: GitHub.