{"record":{"id":"ce5018f0064967f1","repo":"BigPizzaV3/CodexPlusPlus","slug":"sidebar-catalog-entry-rows-must-be-an-array","errorCode":null,"errorMessage":"sidebar catalog entry rows must be an array","messagePattern":"sidebar catalog entry rows must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-data/src/provider_sync.rs","lineNumber":2723,"sourceCode":"                .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            }\n            let rows = entry\n                .get(\"rows\")\n                .and_then(Value::as_array)\n                .ok_or_else(|| anyhow::anyhow!(\"sidebar catalog entry rows must be an array\"))?;\n            for row in rows {\n                let row_id = row\n                    .get(\"thread_id\")\n                    .and_then(Value::as_str)\n                    .ok_or_else(|| anyhow::anyhow!(\"sidebar catalog row is missing thread_id\"))?;\n                if row_id != thread_id {\n                    anyhow::bail!(\"sidebar catalog row thread_id does not match snapshot\");\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\nconst SIDEBAR_CATALOG_TABLES: [&str; 3] = [\n    \"local_thread_catalog\",\n    \"thread_timeline_ledger\",\n    \"local_thread_catalog_scan_entries\",","sourceCodeStart":2705,"sourceCodeEnd":2741,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-data/src/provider_sync.rs#L2705-L2741","documentation":"Each sidebar catalog entry must supply its payload rows as a JSON array under \"rows\". If the field is missing or not an array, the restore cannot iterate rows and aborts before writing anything to the database.","triggerScenarios":"Catalog entry where snapshot entry[\"rows\"] is absent, or is not an array (object, string, number, null), passed to the restore function.","commonSituations":"Hand-crafted snapshot with rows encoded as an object keyed by id; snapshot truncated mid-write; exporter version emitting a different rows container; rows stripped when editing the file.","solutions":["Make \"rows\" a JSON array of row objects in the entry","Use an empty array \"rows\": [] if there is nothing to restore for this entry","Re-export the snapshot from the app to get the correct rows shape","Validate with jq: jq '.catalog[0].rows | type' should print \"array\""],"exampleFix":"// before\n\"rows\": { \"abc\": { \"thread_id\": \"...\" } }\n// after\n\"rows\": [ { \"thread_id\": \"...\" } ]","handlingStrategy":"validation","validationCode":"for (const e of snapshot.catalog ?? []) {\n  if (!Array.isArray(e?.rows)) throw new Error('entry rows must be an array');\n}","typeGuard":"function entryHasRows(e) {\n  return typeof e === 'object' && e !== null && Array.isArray(e.rows);\n}","tryCatchPattern":"try {\n  restore_sidebar_snapshot(&snapshot, codex_home)?;\n} catch (e) {\n  if (String(e).includes('rows must be an array')) {\n    normalizeRowsToArray(snapshot); // convert or default to [], retry\n  }\n}","preventionTips":["Always serialize rows as arrays in exporters","Default missing rows to [] rather than omitting the key","Validate snapshot shape before restore","Keep row schema tests alongside export code"],"tags":["json","schema","sidebar","catalog"],"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"}