{"record":{"id":"2299042bacaffefe","repo":"BigPizzaV3/CodexPlusPlus","slug":"must-be-a-json-object","errorCode":null,"errorMessage":"{} must be a JSON object","messagePattern":"(.+?) must be a JSON object","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/codex_app_state.rs","lineNumber":182,"sourceCode":"            );\n        }\n    }\n}\n\nfn load_global_state(home: &Path) -> anyhow::Result<Option<Map<String, Value>>> {\n    let path = state_path(home);\n    if !path.exists() {\n        return Ok(None);\n    }\n    let text =\n        fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n    let value: Value = serde_json::from_str(&text)\n        .with_context(|| format!(\"failed to parse {}\", path.display()))?;\n    value\n        .as_object()\n        .cloned()\n        .map(Some)\n        .ok_or_else(|| anyhow::anyhow!(\"{} must be a JSON object\", path.display()))\n}\n\nfn load_snapshot(home: &Path) -> anyhow::Result<Option<Map<String, Value>>> {\n    let path = snapshot_path(home);\n    if !path.exists() {\n        return Ok(None);\n    }\n    let text =\n        fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n    let value: Value = serde_json::from_str(&text)\n        .with_context(|| format!(\"failed to parse {}\", path.display()))?;\n    let state = value\n        .get(\"state\")\n        .and_then(Value::as_object)\n        .or_else(|| value.as_object())\n        .cloned()\n        .unwrap_or_default();\n    Ok(Some(state))","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/codex_app_state.rs#L164-L200","documentation":"load_state (crates/codex-plus-core/src/codex_app_state.rs:182) reads the persisted codex-app state file, parses it as JSON successfully, and then requires the top-level value to be an object; anything else (array, string, number, bool, null) yields '<path> must be a JSON object'. Because parsing already succeeded, this is a shape/type error, not a syntax error — the file is valid JSON of the wrong structure.","triggerScenarios":"Loading a home directory whose state JSON was written by a different tool/version with a non-object root (e.g. a top-level array or a bare string), or hand-edited into that shape; the parallel load_snapshot() applies the same rule to the snapshot file.","commonSituations":"Manual edits to the state file; a migration or external script writing scalar/array JSON to the same path; state file shared/synced across machines with mismatched tool versions.","solutions":["Inspect the named file — the message embeds the full path — and confirm its top-level type (jq type file.json)","Restore a known-good object-shaped file (e.g. '{}') or the snapshot counterpart if available","Delete the offending state file so it is treated as absent (path.exists() returns Ok(None)) and gets recreated with the correct shape","Find and fix whatever wrote the wrong shape (external script, older/newer version) so it does not recur"],"exampleFix":"# before: state file is a top-level array\n$ cat ~/.codex-plus/state.json\n[{\"theme\":\"dark\"}]\n\n# after: top-level JSON object\n$ echo '{}' > ~/.codex-plus/state.json","handlingStrategy":"validation","validationCode":"// Pre-validate shape before relying on load_state\nlet text = std::fs::read_to_string(&path)?;\nlet v: serde_json::Value = serde_json::from_str(&text)?;\nensure!(v.is_object(), \"{} must hold a JSON object\", path.display());\n// only now hand the path to the library","typeGuard":"fn is_json_object(v: &serde_json::Value) -> bool { v.is_object() }","tryCatchPattern":"match load_state(home) {\n    Ok(state) => Ok(state),\n    Err(e) if e.to_string().contains(\"must be a JSON object\") => {\n        // shape is wrong but file is absent-safe to reset: back it up and start fresh\n        let _ = std::fs::rename(&path, path.with_extension(\"json.bak\"));\n        Ok(None) // treated as no state\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Only write objects as the state root; never arrays or scalars","Back up state files before external tools touch them","Snapshot state (the codebase keeps a snapshot counterpart) so recovery is possible","Validate external input before persisting it to the state path"],"tags":["json","state-persistence","schema","file-format"],"backgroundTag":"schema-validation-failed","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}