{"record":{"id":"6c66d501abef3d41","repo":"Kuberwastaken/claurst","slug":"hooks-event-name-must-be-an-array","errorCode":null,"errorMessage":"hooks.{event_name} must be an array","messagePattern":"hooks\\.(.+?) must be an array","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-rust/crates/core/src/import_config.rs","lineNumber":715,"sourceCode":"            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\n                .get(\"hooks\")\n                .and_then(Value::as_array)\n                .ok_or_else(|| anyhow!(\"hooks.{event_name}[].hooks must be an array\"))?;\n            for hook in hooks {\n                let hook_obj = hook\n                    .as_object()\n                    .ok_or_else(|| anyhow!(\"hooks.{event_name}[].hooks[] must be an object\"))?;","sourceCodeStart":697,"sourceCodeEnd":733,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/core/src/import_config.rs#L697-L733","documentation":"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.","triggerScenarios":"Importing config where an event maps to a single object instead of an array, e.g. \"hooks\": {\"Stop\": {\"matcher\": \"*\", ...}} or {\"Stop\": \"echo done\"}.","commonSituations":"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.","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"],"exampleFix":"// before\n{ \"hooks\": { \"Stop\": { \"matcher\": \"*\", \"hooks\": [{\"command\": \"lint\"}] } } }\n// after\n{ \"hooks\": { \"Stop\": [ { \"matcher\": \"*\", \"hooks\": [{\"command\": \"lint\"}] } ] } }","handlingStrategy":"type-guard","validationCode":"for (const [event, v] of Object.entries(cfg.hooks ?? {})) {\n  if (!Array.isArray(v)) throw new Error(`hooks.${event} must be an array of hook groups`);\n}","typeGuard":"const isEventGroups = (v) => Array.isArray(v) && v.every((g) => typeof g === 'object' && g !== null);","tryCatchPattern":"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; } }","preventionTips":["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"],"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"}