{"record":{"id":"67b2c2ff085630e0","repo":"xai-org/grok-build","slug":"hooks-is-not-a-json-array","errorCode":null,"errorMessage":"{}: hooks.{} is not a JSON array","messagePattern":"(.+?): hooks\\.(.+?) is not a JSON array","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/claude_import.rs","lineNumber":1097,"sourceCode":"    // re-written iff dirty, even when count == 0.\n    let mut dirty = false;\n    for item in new_hooks {\n        let ImportableItem::Hook {\n            event,\n            matcher,\n            command,\n            timeout,\n        } = item\n        else {\n            continue;\n        };\n\n        let groups = hooks_obj\n            .entry(event.clone())\n            .or_insert_with(|| serde_json::json!([]))\n            .as_array_mut()\n            .ok_or_else(|| {\n                anyhow::anyhow!(\"{}: hooks.{} is not a JSON array\", target.display(), event)\n            })?;\n\n        // Dedup on `(event, matcher, command)`. If a matching entry already\n        // exists, update its `timeout` in place to the new value (so a re-import\n        // with a changed timeout reflects in the output) and skip adding a new\n        // group. Otherwise append a new group below.\n        //\n        // Invariant: `extract_hooks_from_settings_file` filters empty matcher\n        // strings to `None`, so the existing-matcher comparison only needs to\n        // distinguish `None` from `Some(s)`; we no longer need a defensive\n        // `(Some(\"\"), None)` arm.\n        let mut updated = false;\n        for g in groups.iter_mut() {\n            let existing_matcher = g.get(\"matcher\").and_then(|v| v.as_str());\n            let matcher_matches = match (existing_matcher, matcher.as_deref()) {\n                (None, None) => true,\n                (Some(a), Some(b)) => a == b,\n                _ => false,","sourceCodeStart":1079,"sourceCodeEnd":1115,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/claude_import.rs#L1079-L1115","documentation":"For each event in the incoming hooks config, apply_hooks_to_dir gets or creates hooks.<event> and requires it to be a JSON array of matcher groups. If the event key exists with a non-array value (object, string, etc.), appending/deduping groups cannot proceed and this error is thrown.","triggerScenarios":"apply_import merges hooks where an existing settings.json has \"hooks\": {\"PreToolUse\": {...}} (object) or \"hooks\": {\"PostToolUse\": \"disabled\"} instead of an array of groups.","commonSituations":"Schema drift between Claude Code versions (event groups stored as objects in older formats); manual editing; another tool writing per-event config as an object.","solutions":["Change hooks.<event> in settings.json to an array of group objects, e.g. \"PreToolUse\": [{\"matcher\": \"*\", \"hooks\": [...]}]","Delete the malformed hooks.<event> key and re-run the import to rebuild it","Migrate from the old object-shaped event config format to the array format"],"exampleFix":"// before\n{\"hooks\": {\"PreToolUse\": {\"matcher\": \"*\"}}}\n// after\n{\"hooks\": {\"PreToolUse\": [{\"matcher\": \"*\", \"hooks\": []}]}}","handlingStrategy":"type-guard","validationCode":"for ev in [\"PreToolUse\", \"PostToolUse\", \"Stop\"] {\n    let bad = doc.pointer(&format!(\"/hooks/{ev}\"))\n        .map_or(false, |g| !g.is_array());\n    if bad { return Err(format!(\"hooks.{ev} must be an array\").into()); }\n}","typeGuard":"fn event_groups_ok(hooks: &serde_json::Value) -> bool {\n    hooks.as_object().map_or(true, |m| {\n        m.values().all(|g| g.is_array())\n    })\n}","tryCatchPattern":"match apply_import(...) {\n    Err(e) if e.to_string().contains(\"is not a JSON array\") => {\n        // parse the event name from the message, reset that key to [], retry\n        let mut v: serde_json::Value = serde_json::from_str(&raw)?;\n        v[\"hooks\"][event] = serde_json::json!([]);\n        serde_json::to_writer_pretty(&mut file, &v)?;\n        apply_import(...)\n    }\n    r => r,\n}","preventionTips":["Store each hooks event as an array of matcher groups","Migrate legacy object-shaped event configs to arrays","Run schema validation on settings.json before import"],"tags":["json","config","schema"],"backgroundTag":"json-root-not-object","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}