Kuberwastaken/claurst · error · anyhow::Error
hooks. must be an array
Error message
hooks.{event_name} must be an array What it means
In parse_hooks, each event value inside the hooks object must be an array of hook-group objects. If the value for an event key is not an array, this error names the event (hooks.{event_name}) and aborts import.
Solutions
- Wrap the hook group (or command) in a JSON array under the event key
- If it is a single hook, wrap the existing object in brackets
- Validate hooks shape against Claude Code's documented hooks schema before importing
Example fix
// before
{ "hooks": { "Stop": { "matcher": "*", "hooks": [{"command": "lint"}] } } }
// after
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [{"command": "lint"}] } ] } } Defensive patterns
Strategy: type-guard
Validate before calling
for (const [event, v] of Object.entries(cfg.hooks ?? {})) {
if (!Array.isArray(v)) throw new Error(`hooks.${event} must be an array of hook groups`);
} Type guard
const isEventGroups = (v) => Array.isArray(v) && v.every((g) => typeof g === 'object' && g !== null);
Try / catch
try { importConfig(path) } catch (e) { const m = String(e).match(/hooks\.(\S+) must be an array/); if (m) { wrapEventInArray(path, m[1]); } else { throw e; } } Prevention
- Always wrap hook groups in an array, even for a single hook
- Use the documented shape: event -> [ { matcher, hooks: [...] } ]
- Re-check hook arrays after automated config merging
When it happens
Trigger: Importing config where an event maps to a single object instead of an array, e.g. "hooks": {"Stop": {"matcher": "*", ...}} or {"Stop": "echo done"}.
Common situations: Users writing one hook directly under the event key without wrapping it in an array, or merging hooks from two files where one got collapsed to a single object.
Related errors
- hooks must be an object
- hooks. [] must be an object
- hooks. [].hooks must be an array
- hooks. [].hooks[] must be an object
- mcpServers must be an object
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/6c66d501abef3d41.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/import_config.rs:715
server_type,
// Imported from a user-chosen config (e.g. Claude Desktop): trusted.
origin: Default::default(),
});
}
Ok(servers)
}
fn parse_hooks(value: &Value) -> Result<HashMap<HookEvent, Vec<HookEntry>>> {
let Some(obj) = value.as_object() else {
return Err(anyhow!("hooks must be an object"));
};
let mut out = HashMap::new();
for (event_name, event_value) in obj {
let event = parse_hook_event(event_name)?;
let entries = event_value
.as_array()
.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"))?;View on GitHub (pinned to b0637c97ec)