Hmbown/CodeWhale · error

catalog cache unavailable

Error message

catalog cache unavailable

What it means

`ensure_cache_loaded` lazily loads the on-disk provider-catalog cache into a process-global `CACHE` behind a `RwLock`. If acquiring the write lock fails, the RwLock is poisoned — meaning a thread panicked while holding it — and the library surfaces the opaque 'catalog cache unavailable' error rather than risk reading inconsistent cache state.

Solutions

  1. Restart the process — RwLock poisoning is process-global and clears only on restart.
  2. Reproduce the original panic (it appears earlier in logs) and fix that root-cause bug; this error is only the echo.
  3. Check the catalog cache file for malformed content that could make the loading code panic.
Defensive patterns

Strategy: try-catch

Try / catch

match ensure_cache_loaded() {
    Err(e) if e.to_string().contains("catalog cache unavailable") => {
        // poisoned lock: skip cache, go straight to network refresh, log for restart
        log::warn("catalog cache poisoned; bypassing until restart");
        refresh_from_network()
    }
    r => r,
}

Prevention

When it happens

Trigger: `cached_entry_for_route` (via `ensure_cache_loaded`) is called after some earlier thread panicked while mutating `CACHE` (e.g. a bug in cache deserialization/persist code), leaving the RwLock poisoned for the rest of the process.

Common situations: A latent panic in cache-loading code runs once early in the session; every later catalog lookup then fails with this error even though the disk file is fine; usually seen only after another suppressed panic.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/d35bab318b759405. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/provider_catalog_live.rs:684

fn load_from_disk() -> Option<ProviderCatalogCache> {
    let path = cache_path()?;
    if !path.is_file() {
        return None;
    }
    let lock_file = open_cache_lock(&cache_lock_path(&path)).ok()?;
    let lock = fd_lock::RwLock::new(lock_file);
    let _guard = lock.read().ok()?;
    load_from_disk_unlocked(&path)
}

fn ensure_cache_loaded() -> Result<()> {
    if DISK_LOADED.load(Ordering::Acquire) {
        return Ok(());
    }
    let mut cache = CACHE
        .write()
        .map_err(|_| anyhow::anyhow!("catalog cache unavailable"))?;
    if DISK_LOADED.load(Ordering::Acquire) {
        return Ok(());
    }
    if let Some(path) = cache_path() {
        match fs::symlink_metadata(&path) {
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
            Err(err) => return Err(err.into()),
            Ok(_) => {
                let loaded = load_from_disk().context("invalid provider catalog cache")?;
                for (key, entry) in loaded.entries {
                    if cache
                        .entries
                        .get(&key)
                        .is_none_or(|local| entry.fetched_at >= local.fetched_at)
                    {
                        cache.entries.insert(key, entry);
                    }
                }

View on GitHub (pinned to 433685b202)