Kuberwastaken/claurst · error
hooks must be an object
Error message
hooks must be an object
What it means
The imported config's top-level `hooks` value is not a JSON object (e.g. it is an array, string, or scalar). parse_hooks pattern-matches value.as_object() and rejects anything else; the hook map shape expected is { "PreToolUse": [...], ... }.
Solutions
- Make "hooks" a JSON object keyed by event name (PreToolUse, PostToolUse, Stop, PostModelTurn, UserPromptSubmit, Notification)
- Remove the hooks key if no hooks should be imported
- Convert array-style hook lists into the event-keyed object shape
Example fix
// before
{ "hooks": ["echo done"] }
// after
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [ { "command": "echo done" } ] } ] } } Defensive patterns
Strategy: validation
Validate before calling
if (cfg.hooks !== undefined && (typeof cfg.hooks !== 'object' || Array.isArray(cfg.hooks) || cfg.hooks === null)) {
throw new Error('hooks must be a JSON object keyed by event name');
} Type guard
const isHooksMap = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every(Array.isArray);
Try / catch
try { importConfig(path) } catch (e) { if (String(e).includes('hooks must be an object')) { fixHooksShape(path); } else { throw e; } } Prevention
- Keep hooks as an object keyed by event name, never a list or string
- Delete the hooks key instead of setting it to null to disable hooks
- Validate the event keys against the supported set before importing
When it happens
Trigger: Importing a settings file where "hooks" is null, a string, or an array instead of an object like {"PreToolUse": [...]}.
Common situations: A hooks key set to null to disable hooks before import, config fragments copied from a tool that stores hooks as a flat list, or JSON corruption from manual edits.
Related errors
- hooks. must be an array
- 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/5aff39de3d4089aa.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/import_config.rs:708
servers.push(McpServerConfig {
name: name.clone(),
command,
args,
env,
url,
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_objView on GitHub (pinned to b0637c97ec)