{"record":{"id":"ad254400d703f58a","repo":"BigPizzaV3/CodexPlusPlus","slug":"sidebar-catalog-row-is-missing-thread-id","errorCode":null,"errorMessage":"sidebar catalog row is missing thread_id","messagePattern":"sidebar catalog row is missing thread_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-data/src/provider_sync.rs","lineNumber":2728,"sourceCode":"            }\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\",\n];\n\nfn snapshot_thread_from_global_state(\n    codex_home: &Path,\n    thread_id: &str,","sourceCodeStart":2710,"sourceCodeEnd":2746,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-data/src/provider_sync.rs#L2710-L2746","documentation":"Every row in a sidebar catalog entry must carry a string \"thread_id\" so the restore can verify the row belongs to the snapshot's thread. A row without the field aborts the restore. This prevents injecting rows for unrelated threads through a snapshot.","triggerScenarios":"A row object inside entry[\"rows\"] that has no \"thread_id\" key or whose value is not a string, passed to the sidebar snapshot restore function.","commonSituations":"Rows copied from another snapshot or table without their thread_id; hand-edited rows; partial row objects assembled manually; exporter version with differently shaped rows.","solutions":["Add \"thread_id\": \"<same id as snapshot thread_id>\" to each row object","Ensure every row's thread_id matches the snapshot's top-level thread_id exactly (see also the mismatch error)","Re-export the snapshot so rows are complete","Drop rows that are missing thread_id instead of restoring partial data"],"exampleFix":"// before\n{ \"title\": \"My thread\", \"updated_at\": 123 }\n// after\n{ \"thread_id\": \"3f9c...\", \"title\": \"My thread\", \"updated_at\": 123 }","handlingStrategy":"validation","validationCode":"const tid = snapshot.thread_id;\nfor (const e of snapshot.catalog ?? []) {\n  for (const row of e.rows ?? []) {\n    if (typeof row?.thread_id !== 'string' || row.thread_id !== tid) {\n      throw new Error('row thread_id missing or mismatched');\n    }\n  }\n}","typeGuard":"function rowHasThreadId(row, tid) {\n  return typeof row?.thread_id === 'string' && row.thread_id === tid;\n}","tryCatchPattern":"try {\n  restore_sidebar_snapshot(&snapshot, codex_home)?;\n} catch (e) {\n  if (String(e).includes('row is missing thread_id')) {\n    dropOrRepairRowsWithoutThreadId(snapshot);\n  }\n}","preventionTips":["Filter rows by the snapshot's thread_id at export time","Never copy rows between snapshots of different threads","Validate every row against the snapshot thread_id before restore","Reject partial row objects instead of restoring incomplete data"],"tags":["json","validation","sidebar","catalog"],"backgroundTag":"missing-required-argument","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"}