{"record":{"id":"5aff39de3d4089aa","repo":"Kuberwastaken/claurst","slug":"hooks-must-be-an-object","errorCode":null,"errorMessage":"hooks must be an object","messagePattern":"hooks must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-rust/crates/core/src/import_config.rs","lineNumber":708,"sourceCode":"\n        servers.push(McpServerConfig {\n            name: name.clone(),\n            command,\n            args,\n            env,\n            url,\n            server_type,\n            // Imported from a user-chosen config (e.g. Claude Desktop): trusted.\n            origin: Default::default(),\n        });\n    }\n\n    Ok(servers)\n}\n\nfn parse_hooks(value: &Value) -> Result<HashMap<HookEvent, Vec<HookEntry>>> {\n    let Some(obj) = value.as_object() else {\n        return Err(anyhow!(\"hooks must be an object\"));\n    };\n    let mut out = HashMap::new();\n    for (event_name, event_value) in obj {\n        let event = parse_hook_event(event_name)?;\n        let entries = event_value\n            .as_array()\n            .ok_or_else(|| anyhow!(\"hooks.{event_name} must be an array\"))?;\n        let mut hook_entries = Vec::new();\n        for entry in entries {\n            let entry_obj = entry\n                .as_object()\n                .ok_or_else(|| anyhow!(\"hooks.{event_name}[] must be an object\"))?;\n            let matcher = entry_obj\n                .get(\"matcher\")\n                .and_then(Value::as_str)\n                .unwrap_or(\"*\")\n                .to_string();\n            let hooks = entry_obj","sourceCodeStart":690,"sourceCodeEnd":726,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/core/src/import_config.rs#L690-L726","documentation":"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\": [...], ... }.","triggerScenarios":"Importing a settings file where \"hooks\" is null, a string, or an array instead of an object like {\"PreToolUse\": [...]}.","commonSituations":"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.","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"],"exampleFix":"// before\n{ \"hooks\": [\"echo done\"] }\n// after\n{ \"hooks\": { \"Stop\": [ { \"matcher\": \"*\", \"hooks\": [ { \"command\": \"echo done\" } ] } ] } }","handlingStrategy":"validation","validationCode":"if (cfg.hooks !== undefined && (typeof cfg.hooks !== 'object' || Array.isArray(cfg.hooks) || cfg.hooks === null)) {\n  throw new Error('hooks must be a JSON object keyed by event name');\n}","typeGuard":"const isHooksMap = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every(Array.isArray);","tryCatchPattern":"try { importConfig(path) } catch (e) { if (String(e).includes('hooks must be an object')) { fixHooksShape(path); } else { throw e; } }","preventionTips":["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"],"tags":["json","config-import","validation","hooks"],"backgroundTag":"config-type-mismatch","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}