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
- Change `run` to a single string: run = "echo hi".
- Or to an array of strings: run = ["echo one", "echo two"].
- 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
- Always quote run commands as TOML strings.
- Use arrays of quoted strings for multiple commands.
- Validate mise.toml against the published JSON schema.
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
- backend must be a string or table
- expected a `run` command
- expected value of to be a , got
- [history] exclude in
- [history] in is not a table
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)