Hmbown/CodeWhale · critical

bundled model catalog must parse

Error message

bundled model catalog must parse

What it means

Panic loading the model catalog compiled into the binary: `BUNDLED_CATALOG_JSON` is `include_str!("../assets/model_catalog.bundled.json")` (model_catalog.rs:16) and `bundled_catalog()` parses it into `CatalogCache`. Because the asset is checked in at build time, failure means the JSON file and the `CatalogCache` deserialization type are out of sync — a renamed/retyped field, truncated file, or schema-version mismatch — not a runtime condition on the user's machine.

Source

Thrown at crates/tui/src/model_catalog.rs:166

#[must_use]
pub fn resolved_max_output(model: &str) -> Option<u32> {
    resolved_entry(model).and_then(|entry| entry.max_output)
}

#[must_use]
pub fn resolved_supports_reasoning(model: &str) -> Option<bool> {
    resolved_entry(model).and_then(|entry| entry.supports_reasoning)
}

#[must_use]
#[cfg_attr(test, allow(dead_code))]
pub fn resolved_usd_pricing(model: &str) -> Option<(f64, f64)> {
    let entry = resolved_entry(model)?;
    Some((entry.input_usd_per_million?, entry.output_usd_per_million?))
}

pub fn bundled_catalog() -> CatalogCache {
    serde_json::from_str(BUNDLED_CATALOG_JSON).expect("bundled model catalog must parse")
}

fn catalog_cache_read_path() -> Result<PathBuf> {
    Ok(codewhale_config::resolve_state_dir("catalog")?.join(OPENROUTER_CACHE_FILE))
}

pub fn load_cached() -> Option<CatalogCache> {
    let path = catalog_cache_read_path().ok()?;
    let raw = std::fs::read_to_string(path).ok()?;
    serde_json::from_str(&raw).ok()
}

#[cfg(test)]
static TEST_CATALOG_LOCK: std::sync::LazyLock<std::sync::Mutex<()>> =
    std::sync::LazyLock::new(|| std::sync::Mutex::new(()));

#[cfg(test)]
pub(crate) fn test_catalog_lock() -> std::sync::MutexGuard<'static, ()> {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Fix the asset or the struct so they agree, then rebuild — verify with a direct parse of the asset in a scratch test.
  2. Add/keep a smoke unit test calling `bundled_catalog()` so schema drift fails `cargo test -p codewhale-tui` instead of the first user keystroke.
  3. If the binary came from elsewhere, reinstall/rebuild from a clean checkout.
  4. Cross-check the catalog `schema_version` constant between the asset and code.

Example fix

// before
serde_json::from_str(BUNDLED_CATALOG_JSON).expect("bundled model catalog must parse")

// after: keep the panic but pin the invariant with a CI test so drift is caught pre-release
#[test]
fn bundled_catalog_parses() {
    let catalog = bundled_catalog();
    assert!(!catalog.entries().is_empty());
}
Defensive patterns

Strategy: fallback

Validate before calling

// Prefer the on-disk cache when present; it survives schema drift in the binary
if let Some(cached) = model_catalog::load_cached() {
    // use cached entries instead of relying on the bundled asset
}

Try / catch

let catalog = std::panic::catch_unwind(model_catalog::bundled_catalog)
    .ok()
    .or_else(model_catalog::load_cached);
let catalog = catalog.expect("neither bundled nor cached model catalog is usable");

Prevention

When it happens

Trigger: Editing `crates/tui/assets/model_catalog.bundled.json` or the `CatalogCache`/entry structs without keeping both in sync; a partial merge of the asset; release automation regenerating the asset with a different schema. The first `resolved_*` call after such a build panics at model_catalog.rs:166.

Common situations: Branches that changed pricing/capability fields; a stale or corrupted checkout mixing an old asset with new code; CI builds that never execute the catalog parse path before shipping.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/caa4aa38d179fa5e. Report an issue: GitHub.