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
- 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
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
- 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
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
- {}: root is not a JSON object
- {}: hooks is not a JSON object
- Failed to create agent config: {e}
- Invalid ACP content blocks: {e}
- Invalid ACP content blocks in "content": {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/67b2c2ff085630e0.
Report an issue: GitHub.