{"record":{"id":"d59c5f995e386e74","repo":"Kuberwastaken/claurst","slug":"hooks-event-name-hooks-must-be-an-object","errorCode":null,"errorMessage":"hooks.{event_name}[].hooks[] must be an object","messagePattern":"hooks\\.(.+?)\\[\\]\\.hooks\\[\\] must be an object","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-rust/crates/core/src/import_config.rs","lineNumber":733,"sourceCode":"            .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\"))?;\n                let command = hook_obj\n                    .get(\"command\")\n                    .and_then(Value::as_str)\n                    .ok_or_else(|| anyhow!(\"hooks.{event_name} hook is missing command\"))?\n                    .to_string();\n                hook_entries.push(HookEntry {\n                    command,\n                    tool_filter: if matcher == \"*\" { None } else { Some(matcher.clone()) },\n                    blocking: false,\n                });\n            }\n        }\n        out.insert(event, hook_entries);\n    }\n    Ok(out)\n}\n\nfn parse_hook_event(name: &str) -> Result<HookEvent> {","sourceCodeStart":715,"sourceCodeEnd":751,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/core/src/import_config.rs#L715-L751","documentation":"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.","triggerScenarios":"Importing a group like {\"hooks\": [\"echo hi\"]} where the inner array holds raw strings or nulls instead of objects.","commonSituations":"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.","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\": \"...\" } ]"],"exampleFix":"// before\n{ \"hooks\": { \"Stop\": [ { \"matcher\": \"*\", \"hooks\": [\"echo hi\"] } ] } }\n// after\n{ \"hooks\": { \"Stop\": [ { \"matcher\": \"*\", \"hooks\": [ { \"command\": \"echo hi\" } ] } ] } }","handlingStrategy":"type-guard","validationCode":"for (const groups of Object.values(cfg.hooks ?? {})) {\n  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');\n}","typeGuard":"const isHookObj = (h) => typeof h === 'object' && h !== null && !Array.isArray(h) && typeof h.command === 'string';","tryCatchPattern":"try { importConfig(path) } catch (e) { if (/hooks\\[\\] must be an object/.test(String(e))) { wrapInnerHooksAsObjects(path); } else { throw e; } }","preventionTips":["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"],"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"}