Hmbown/CodeWhale · error

catalog cache unavailable

Error message

catalog cache unavailable

What it means

ensure_cache_loaded takes a write lock on the in-memory catalog CACHE (a RwLock) while first loading from disk. If the lock is poisoned — a previous thread panicked while holding it — lock().write() returns Err and this error is thrown instead of proceeding.

Solutions

  1. Fix the underlying panic that poisoned the CACHE lock (check earlier panics in logs)
  2. Restart the TUI process to get a fresh, unpoisoned lock
  3. Wrap cache mutations in catch_unwind or restructure so no code panics while holding the lock
  4. If persistent, delete the on-disk catalog cache to rule out malformed-data-driven panics

Example fix

// before
let mut cache = CACHE.write().map_err(|_| anyhow::anyhow!("catalog cache unavailable"))?;
// after
let mut cache = CACHE.write().unwrap_or_else(|e| e.into_inner()); // recover from poison
Defensive patterns

Strategy: retry

Try / catch

match ensure_cache_loaded() {
    Err(e) if e.to_string().contains("catalog cache unavailable") => {
        // lock poisoned: restart session or fall back to network refresh
        return fallback_to_live_catalog();
    }
    Ok(()) => {},
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling cached_entry_for_route (which calls ensure_cache_loaded) after any panic occurred in another thread while mutating the CACHE map, e.g. during disk load or scope writes.

Common situations: After a panic elsewhere in catalog code; long-running TUI sessions that hit an earlier bug and now permanently fail catalog cache reads.

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@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/d35bab318b759405. Report an issue: GitHub.

Appendix: source

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

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 73e0f67d83)