BigPizzaV3/CodexPlusPlus · error

sidebar snapshot catalog must be an array

Error message

sidebar snapshot catalog must be an array

What it means

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.

Solutions

  1. Change "catalog" in the snapshot JSON to an array of entry objects
  2. Remove the "catalog" key entirely if there is nothing to restore (it is optional)
  3. Re-export the snapshot from CodexPlusPlus instead of hand-crafting it
  4. Validate with jq: jq '.catalog | type' should print "array"

Example fix

// before
"catalog": { "threads": [ ... ] }
// after
"catalog": [ { "table": "threads", "db_path": "...", "rows": [ ... ] } ]
Defensive patterns

Strategy: validation

Validate before calling

if (snapshot.catalog !== undefined && !Array.isArray(snapshot.catalog)) {
  throw new Error('snapshot.catalog must be an array');
}

Type guard

function hasValidCatalog(v) {
  return v.catalog === undefined || Array.isArray(v.catalog);
}

Try / catch

try {
  restore_sidebar_snapshot(&snapshot, codex_home)?;
} catch (e) {
  if (String(e).includes('catalog must be an array')) {
    fixOrDropCatalogField(snapshot); // drop key or convert to array, then retry
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/17240a8ed60f0259. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-data/src/provider_sync.rs:2701

        .unwrap_or_default();
    let mut restored = restore_thread_to_global_state(codex_home, thread_id, snapshot)?;
    restored += restore_thread_to_catalog_dbs(codex_home, thread_id, snapshot)?;
    Ok(restored)
}

pub fn validate_thread_sidebar_snapshot(
    codex_home: &Path,
    snapshot: &Value,
) -> anyhow::Result<()> {
    let thread_id = snapshot
        .get("thread_id")
        .and_then(Value::as_str)
        .filter(|id| !id.trim().is_empty())
        .ok_or_else(|| anyhow::anyhow!("sidebar snapshot is missing thread_id"))?;
    if let Some(catalog) = snapshot.get("catalog") {
        let entries = catalog
            .as_array()
            .ok_or_else(|| anyhow::anyhow!("sidebar snapshot catalog must be an array"))?;
        let allowed_paths = sidebar_catalog_db_paths(codex_home)?;
        for entry in entries {
            let table = entry
                .get("table")
                .and_then(Value::as_str)
                .ok_or_else(|| anyhow::anyhow!("sidebar catalog entry is missing table"))?;
            if !SIDEBAR_CATALOG_TABLES.contains(&table) {
                anyhow::bail!("unsupported sidebar catalog table: {table}");
            }
            let path = entry
                .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");
            }

View on GitHub (pinned to b1ed92e5e4)