{"record":{"id":"17240a8ed60f0259","repo":"BigPizzaV3/CodexPlusPlus","slug":"sidebar-snapshot-catalog-must-be-an-array","errorCode":null,"errorMessage":"sidebar snapshot catalog must be an array","messagePattern":"sidebar snapshot catalog must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-data/src/provider_sync.rs","lineNumber":2701,"sourceCode":"        .unwrap_or_default();\n    let mut restored = restore_thread_to_global_state(codex_home, thread_id, snapshot)?;\n    restored += restore_thread_to_catalog_dbs(codex_home, thread_id, snapshot)?;\n    Ok(restored)\n}\n\npub fn validate_thread_sidebar_snapshot(\n    codex_home: &Path,\n    snapshot: &Value,\n) -> anyhow::Result<()> {\n    let thread_id = snapshot\n        .get(\"thread_id\")\n        .and_then(Value::as_str)\n        .filter(|id| !id.trim().is_empty())\n        .ok_or_else(|| anyhow::anyhow!(\"sidebar snapshot is missing thread_id\"))?;\n    if let Some(catalog) = snapshot.get(\"catalog\") {\n        let entries = catalog\n            .as_array()\n            .ok_or_else(|| anyhow::anyhow!(\"sidebar snapshot catalog must be an array\"))?;\n        let allowed_paths = sidebar_catalog_db_paths(codex_home)?;\n        for entry in entries {\n            let table = entry\n                .get(\"table\")\n                .and_then(Value::as_str)\n                .ok_or_else(|| anyhow::anyhow!(\"sidebar catalog entry is missing table\"))?;\n            if !SIDEBAR_CATALOG_TABLES.contains(&table) {\n                anyhow::bail!(\"unsupported sidebar catalog table: {table}\");\n            }\n            let path = entry\n                .get(\"db_path\")\n                .and_then(Value::as_str)\n                .map(PathBuf::from)\n                .ok_or_else(|| anyhow::anyhow!(\"sidebar catalog entry is missing db_path\"))?;\n            let canonical = fs::canonicalize(&path)?;\n            if !allowed_paths.contains(&canonical) {\n                anyhow::bail!(\"sidebar catalog database is not an allowed Codex database\");\n            }","sourceCodeStart":2683,"sourceCodeEnd":2719,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-data/src/provider_sync.rs#L2683-L2719","documentation":"When a sidebar snapshot contains a \"catalog\" key, its value must be a JSON array of catalog entries. If the key exists but is not an array (object, string, null), the restore fails fast with this error before any database writes. The catalog is optional — absent means nothing to restore — but present-and-malformed is rejected.","triggerScenarios":"Calling the restore function with snapshot[\"catalog\"] set to a non-array JSON value, e.g. an object mapping table names to rows, a string, or null.","commonSituations":"Snapshot written by a different tool/version with a different catalog layout; manual JSON editing converted the array into an object; copy-paste error when assembling a snapshot by hand.","solutions":["Change \"catalog\" in the snapshot JSON to an array of entry objects","Remove the \"catalog\" key entirely if there is nothing to restore (it is optional)","Re-export the snapshot from CodexPlusPlus instead of hand-crafting it","Validate with jq: jq '.catalog | type' should print \"array\""],"exampleFix":"// before\n\"catalog\": { \"threads\": [ ... ] }\n// after\n\"catalog\": [ { \"table\": \"threads\", \"db_path\": \"...\", \"rows\": [ ... ] } ]","handlingStrategy":"validation","validationCode":"if (snapshot.catalog !== undefined && !Array.isArray(snapshot.catalog)) {\n  throw new Error('snapshot.catalog must be an array');\n}","typeGuard":"function hasValidCatalog(v) {\n  return v.catalog === undefined || Array.isArray(v.catalog);\n}","tryCatchPattern":"try {\n  restore_sidebar_snapshot(&snapshot, codex_home)?;\n} catch (e) {\n  if (String(e).includes('catalog must be an array')) {\n    fixOrDropCatalogField(snapshot); // drop key or convert to array, then retry\n  }\n}","preventionTips":["Treat absent catalog as valid; never write catalog: null","Emit catalog as an array in export code","Run a JSON schema check on snapshots before restoring","Keep export/restore versions in sync"],"tags":["json","schema","sidebar","snapshot"],"backgroundTag":"schema-validation-failed","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}