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

A hook table's `run` field must be either a TOML string or an array of strings. When `run` exists but is some other TOML type (integer, boolean, nested table, array of non-strings), from_toml bails with this message.

Solutions

  1. Change `run` to a single string: run = "echo hi".
  2. Or to an array of strings: run = ["echo one", "echo two"].
  3. Ensure array elements are all quoted strings, not numbers or booleans.

Example fix

// before
[[hooks]]
phase = "pretool"
run = { cmd = "echo hi", shell = "bash" }
// after
[[hooks]]
phase = "pretool"
run = "echo hi"
Defensive patterns

Strategy: type-guard

Validate before calling

fn valid_run(v: &toml::Value) -> bool {
  match v { toml::Value::String(_) => true, toml::Value::Array(a) => a.iter().all(|x| x.is_str()), _ => false }
}

Type guard

fn as_string_or_strings(v: &toml::Value) -> Option<Vec<String>> {
  match v {
    toml::Value::String(s) => Some(vec![s.clone()]),
    toml::Value::Array(a) => a.iter().map(|x| x.as_str().map(str::to_string)).collect(),
    _ => None,
  }
}

Try / catch

match load_hooks(config) {
  Err(e) if e.to_string().contains("`run` to be a string") => eprintln!("fix hook run field: {}", e),
  other => other?,
}

Prevention

When it happens

Trigger: Writing `run = 42`, `run = true`, `run = { cmd = "x" }`, or `run = [1, 2]` inside a hook table parsed by from_toml.

Common situations: Quoting mistakes in TOML; copying YAML/JSON-style config; trying to pass structured command options in `run` instead of a plain string or string list.

Related errors


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

Appendix: source

Thrown at src/system/hooks.rs:95

    ) -> 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,
                        config_path: config_path.clone(),
                    })
                }

View on GitHub (pinned to 533346cc37)