BigPizzaV3/CodexPlusPlus · error
sidebar catalog row thread_id does not match snapshot
Error message
sidebar catalog row thread_id does not match snapshot
What it means
Every catalog row must carry a thread_id identical to the snapshot's thread_id being restored. This error is thrown when a row's thread_id differs, i.e. the catalog rows do not correspond to the snapshot, so restoring would attach rows to the wrong thread.
Solutions
- Remove or fix rows whose thread_id disagrees with the snapshot, then retry the restore
- Rebuild the sidebar snapshot from the current database and restore from that fresh snapshot instead of the stale one
- Check for concurrent Codex instances writing .codex-global-state.json during restore; close other instances and retry
Example fix
// before
{"thread_id": "aaa-old", "rows": [{"thread_id": "bbb-new"}]}
// after
{"thread_id": "bbb-new", "rows": [{"thread_id": "bbb-new"}]} Defensive patterns
Strategy: validation
Validate before calling
fn rows_match(entry: &Value, thread_id: &str) -> bool {
entry.get("rows").and_then(Value::as_array).map(|rows| {
rows.iter().all(|r| r.get("thread_id").and_then(Value::as_str) == Some(thread_id))
}).unwrap_or(false)
} Type guard
fn row_thread<'a>(row: &'a Value, expected: &str) -> Option<&'a str> {
row.get("thread_id").and_then(Value::as_str).filter(|t| *t == expected)
} Try / catch
if let Err(e) = restore(&state) {
if e.to_string().contains("does not match snapshot") {
// discard stale snapshot and rebuild from the live database
}
} Prevention
- Snapshot and restore without letting other Codex instances write in between
- Filter rows by thread_id before persisting catalog entries
- Regenerate snapshots after thread deletion/recreation
When it happens
Trigger: Restoring sidebar state where any row in an entry has a 'thread_id' string that does not equal the snapshot thread id — mixed state from concurrent edits, stale rows after thread deletion/recreation, or manual JSON editing.
Common situations: Sidebar data changed between snapshot and restore; rows copied from another snapshot; UUIDs regenerated after re-login or session reset; partially written state from a crash.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Dream Skin theme id does not match directory
- 主题包必须是 32 MiB 以内的普通 ZIP 文件
- 请填写 API 的模型名称
- 官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的…
- 请填写 API 的模型名称
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/27ff68504e63edd4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-data/src/provider_sync.rs:2730
.get("db_path")
.and_then(Value::as_str)
.map(PathBuf::from)
.ok_or_else(|| anyhow::anyhow!("sidebar catalog entry is missing db_path"))?;
let canonical = fs::canonicalize(&path)?;
if !allowed_paths.contains(&canonical) {
anyhow::bail!("sidebar catalog database is not an allowed Codex database");
}
let rows = entry
.get("rows")
.and_then(Value::as_array)
.ok_or_else(|| anyhow::anyhow!("sidebar catalog entry rows must be an array"))?;
for row in rows {
let row_id = row
.get("thread_id")
.and_then(Value::as_str)
.ok_or_else(|| anyhow::anyhow!("sidebar catalog row is missing thread_id"))?;
if row_id != thread_id {
anyhow::bail!("sidebar catalog row thread_id does not match snapshot");
}
}
}
}
Ok(())
}
const SIDEBAR_CATALOG_TABLES: [&str; 3] = [
"local_thread_catalog",
"thread_timeline_ledger",
"local_thread_catalog_scan_entries",
];
fn snapshot_thread_from_global_state(
codex_home: &Path,
thread_id: &str,
) -> anyhow::Result<Value> {
let path = codex_home.join(".codex-global-state.json");View on GitHub (pinned to b1ed92e5e4)