Kuberwastaken/claurst · error · anyhow::Error
hooks. [].hooks[] must be an object
Error message
hooks.{event_name}[].hooks[] must be an object What it means
In an imported hooks config, an element of a hook event array is not a JSON object. Each entry must be an object like { "matcher": "...", "hooks": [...] }; a non-object element (string, number) triggers this error.
Solutions
- Wrap each command string in an object: {"command": "<shell command>"}
- Remove null or otherwise non-object elements from the inner hooks array
- Follow the documented inner shape: hooks: [ { "command": "..." } ]
Example fix
// before
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": ["echo hi"] } ] } }
// after
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [ { "command": "echo hi" } ] } ] } } Defensive patterns
Strategy: type-guard
Validate before calling
for (const groups of Object.values(cfg.hooks ?? {})) {
for (const g of groups) for (const h of g.hooks ?? []) if (typeof h !== 'object' || h === null) throw new Error('each hook must be an object with a command');
} Type guard
const isHookObj = (h) => typeof h === 'object' && h !== null && !Array.isArray(h) && typeof h.command === 'string';
Try / catch
try { importConfig(path) } catch (e) { if (/hooks\[\] must be an object/.test(String(e))) { wrapInnerHooksAsObjects(path); } else { throw e; } } Prevention
- Each element of hooks[].hooks must be an object: { "command": "..." }
- Do not use the shorthand ["echo hi"] form; it is not accepted
- Sanity-check hooks with a JSON schema linter after script-generated edits
When it happens
Trigger: Importing a group like {"hooks": ["echo hi"]} where the inner array holds raw strings or nulls instead of objects.
Common situations: Concise hook syntax copied from blog posts or other tools, sed/script-generated config pushing strings, or JSON where the object braces were lost during editing.
Related errors
- hooks must be an object
- hooks. must be an array
- hooks. [] must be an object
- hooks. [].hooks must be an array
- mcpServers must be an object
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/d59c5f995e386e74.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/import_config.rs:733
.ok_or_else(|| anyhow!("hooks.{event_name} must be an array"))?;
let mut hook_entries = Vec::new();
for entry in entries {
let entry_obj = entry
.as_object()
.ok_or_else(|| anyhow!("hooks.{event_name}[] must be an object"))?;
let matcher = entry_obj
.get("matcher")
.and_then(Value::as_str)
.unwrap_or("*")
.to_string();
let hooks = entry_obj
.get("hooks")
.and_then(Value::as_array)
.ok_or_else(|| anyhow!("hooks.{event_name}[].hooks must be an array"))?;
for hook in hooks {
let hook_obj = hook
.as_object()
.ok_or_else(|| anyhow!("hooks.{event_name}[].hooks[] must be an object"))?;
let command = hook_obj
.get("command")
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("hooks.{event_name} hook is missing command"))?
.to_string();
hook_entries.push(HookEntry {
command,
tool_filter: if matcher == "*" { None } else { Some(matcher.clone()) },
blocking: false,
});
}
}
out.insert(event, hook_entries);
}
Ok(out)
}
fn parse_hook_event(name: &str) -> Result<HookEvent> {View on GitHub (pinned to b0637c97ec)