xai-org/grok-build · error

{}: hooks is not a JSON object

Error message

{}: hooks is not a JSON object

What it means

After ensuring the root is an object, apply_hooks_to_dir fetches or creates the root's 'hooks' key and requires it to be a JSON object whose keys are event names. If 'hooks' exists but is an array, string, or other non-object, merging hook groups is impossible and this error is thrown.

Source

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

            warn!(
                path = %target.display(),
                error = %e,
                "Existing imported-from-claude.json is malformed; replacing with fresh content. \
                 Manual edits in the malformed file will be lost."
            );
            serde_json::json!({})
        }),
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => serde_json::json!({}),
        Err(e) => return Err(e.into()),
    };
    let root_obj = root
        .as_object_mut()
        .ok_or_else(|| anyhow::anyhow!("{}: root is not a JSON object", target.display()))?;
    let hooks_obj = root_obj
        .entry("hooks".to_string())
        .or_insert_with(|| serde_json::json!({}))
        .as_object_mut()
        .ok_or_else(|| anyhow::anyhow!("{}: hooks is not a JSON object", target.display()))?;

    let mut count = 0usize;
    // `dirty` tracks whether we mutated the JSON in any way (including
    // in-place timeout refreshes that don't add new entries). The file is
    // 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

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Edit settings.json so "hooks" is an object keyed by event name, e.g. "hooks": {"PreToolUse": []}
  2. Remove the bad "hooks" key and re-run the import so it is recreated as {}
  3. Validate settings.json against the expected hooks schema before importing

Example fix

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

Strategy: type-guard

Validate before calling

let v: serde_json::Value = serde_json::from_str(&raw)?;
if !v.get("hooks").map_or(true, |h| h.is_object()) {
    return Err("hooks must be a JSON object".into());
}

Type guard

fn hooks_is_object(v: &serde_json::Value) -> bool {
    v.get("hooks").map_or(true, serde_json::Value::is_object)
}

Try / catch

match apply_import(...) {
    Err(e) if e.to_string().contains("hooks is not a JSON object") => {
        // repair: force hooks to {}
        let mut v: serde_json::Value = serde_json::from_str(&raw)?;
        v["hooks"] = serde_json::json!({});
        serde_json::to_writer_pretty(&mut file, &v)?;
        apply_import(...) // retry
    }
    r => r,
}

Prevention

When it happens

Trigger: apply_import encounters a settings.json where "hooks" is set to a non-object value, e.g. "hooks": [] or "hooks": "none".

Common situations: Hand-edited settings.json with wrong hooks shape; older config format that stored hooks as a list; copy-paste from outdated documentation.

Related errors


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