xai-org/grok-build · error
{}: root is not a JSON object
Error message
{}: root is not a JSON object What it means
apply_hooks_to_dir reads the target settings.json, falling back to an empty JSON object when the file is absent, then requires the parsed root to be a JSON object so 'hooks' can be inserted. If the file's top-level JSON value is an array, string, number, or null, the mutation cannot proceed and this error is thrown.
Source
Thrown at crates/codegen/xai-grok-shell/src/claude_import.rs:1069
let target = hooks_dir.join("imported-from-claude.json");
// Read existing JSON if present.
let mut root: serde_json::Value = match std::fs::read_to_string(&target) {
Ok(s) => serde_json::from_str(&s).unwrap_or_else(|e| {
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,
} = itemView on GitHub (pinned to bc7f02eddd)
Solutions
- Inspect the target file and wrap the content in an object, e.g. {} or {"hooks": {}}
- Back up and replace the malformed settings.json with an empty JSON object, then re-run the import
- Fix whatever tool is writing a non-object root to settings.json
Example fix
// before (settings.json)
[]
// after
{} Defensive patterns
Strategy: validation
Validate before calling
// pre-flight check
let raw = std::fs::read_to_string(path)?;
let v: serde_json::Value = serde_json::from_str(&raw)?;
if !v.is_object() {
return Err(format!("{}: root is not an object", path.display()).into());
} Type guard
fn is_json_object(v: &serde_json::Value) -> bool {
v.is_object()
} Try / catch
match apply_import(...) {
Err(e) if e.to_string().contains("root is not a JSON object") => {
std::fs::copy(&target, "settings.json.bak")?;
std::fs::write(&target, "{}")?;
apply_import(...) // retry
}
r => r,
} Prevention
- Validate settings.json is a JSON object before running imports
- Avoid tools that write bare arrays/strings to settings.json
- Keep a backup of settings.json to restore after corruption
When it happens
Trigger: Running apply_import when ~/.claude/settings.json (or the matched dir target) contains valid JSON whose top level is not an object, e.g. the file is '[]' or '"settings"'.
Common situations: Manually edited or corrupted settings.json; a tool that wrote a JSON array; an empty-but-invalid placeholder file; a truncated write from a crash.
Related errors
- {}: hooks is not a JSON object
- {}: hooks.{} is not a JSON array
- 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/4c6d10f04b9332a0.
Report an issue: GitHub.