xai-org/grok-build · error

{}: hooks.{} is not a JSON array

Error message

{}: hooks.{} is not a JSON array

What it means

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.

Source

Thrown at crates/codegen/xai-grok-shell/src/claude_import.rs:1097

    // re-written iff dirty, even when count == 0.
    let mut dirty = false;
    for item in new_hooks {
        let ImportableItem::Hook {
            event,
            matcher,
            command,
            timeout,
        } = item
        else {
            continue;
        };

        let groups = hooks_obj
            .entry(event.clone())
            .or_insert_with(|| serde_json::json!([]))
            .as_array_mut()
            .ok_or_else(|| {
                anyhow::anyhow!("{}: hooks.{} is not a JSON array", target.display(), event)
            })?;

        // Dedup on `(event, matcher, command)`. If a matching entry already
        // exists, update its `timeout` in place to the new value (so a re-import
        // with a changed timeout reflects in the output) and skip adding a new
        // group. Otherwise append a new group below.
        //
        // Invariant: `extract_hooks_from_settings_file` filters empty matcher
        // strings to `None`, so the existing-matcher comparison only needs to
        // distinguish `None` from `Some(s)`; we no longer need a defensive
        // `(Some(""), None)` arm.
        let mut updated = false;
        for g in groups.iter_mut() {
            let existing_matcher = g.get("matcher").and_then(|v| v.as_str());
            let matcher_matches = match (existing_matcher, matcher.as_deref()) {
                (None, None) => true,
                (Some(a), Some(b)) => a == b,
                _ => false,

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Change hooks.<event> in settings.json to an array of group objects, e.g. "PreToolUse": [{"matcher": "*", "hooks": [...]}]
  2. Delete the malformed hooks.<event> key and re-run the import to rebuild it
  3. Migrate from the old object-shaped event config format to the array format

Example fix

// before
{"hooks": {"PreToolUse": {"matcher": "*"}}}
// after
{"hooks": {"PreToolUse": [{"matcher": "*", "hooks": []}]}}
Defensive patterns

Strategy: type-guard

Validate before calling

for ev in ["PreToolUse", "PostToolUse", "Stop"] {
    let bad = doc.pointer(&format!("/hooks/{ev}"))
        .map_or(false, |g| !g.is_array());
    if bad { return Err(format!("hooks.{ev} must be an array").into()); }
}

Type guard

fn event_groups_ok(hooks: &serde_json::Value) -> bool {
    hooks.as_object().map_or(true, |m| {
        m.values().all(|g| g.is_array())
    })
}

Try / catch

match apply_import(...) {
    Err(e) if e.to_string().contains("is not a JSON array") => {
        // parse the event name from the message, reset that key to [], retry
        let mut v: serde_json::Value = serde_json::from_str(&raw)?;
        v["hooks"][event] = serde_json::json!([]);
        serde_json::to_writer_pretty(&mut file, &v)?;
        apply_import(...)
    }
    r => r,
}

Prevention

When it happens

Trigger: apply_import merges hooks where an existing settings.json has "hooks": {"PreToolUse": {...}} (object) or "hooks": {"PostToolUse": "disabled"} instead of an array of groups.

Common situations: 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.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/67b2c2ff085630e0. Report an issue: GitHub.