pnpm/pnpm · critical

store index mutex

Error message

store index mutex

What it means

During `pnpm cache view`, pacquet opens the store's index.db through a shared StoreIndex guarded by a std Mutex. This expect fires when lock() returns Err, i.e. the mutex is poisoned — another thread panicked while holding it. The visible panic is a secondary failure; the first panic earlier in the log is the real defect.

Source

Thrown at pnpm/crates/cli/src/cli_args/cache.rs:207

                        meta_file_paths
                            .push((path_str.replace('\\', "/"), entry.path().to_path_buf()));
                    }
                }
                meta_file_paths.sort();

                // IndexMap preserves insertion order so the JSON key order is
                // deterministic (driven by the sorted file paths), matching
                // pnpm's plain-object output.
                let mut meta_files_by_path = IndexMap::new();

                // pnpm's cacheView opens a writable StoreIndex that creates
                // index.db when absent, so a fresh/empty store reports every
                // version as non-cached rather than erroring. `shared_readonly_in`
                // returns None when index.db does not exist, which we treat the
                // same way: every lookup is a miss.
                let store_index = StoreIndex::shared_readonly_in(&config.store_dir);
                let store_index =
                    store_index.as_ref().map(|index| index.lock().expect("store index mutex"));

                for (file_path, full_path) in meta_file_paths {
                    let Some(meta_object) = load_meta(&full_path) else { continue };
                    let mtime = fs::metadata(&full_path).and_then(|meta| meta.modified()).ok();

                    let mut cached_versions = Vec::new();
                    let mut non_cached_versions = Vec::new();

                    for (version, json_frag) in meta_object.versions.fragments() {
                        let Ok(manifest) = serde_json::from_str::<serde_json::Value>(&json_frag)
                        else {
                            continue;
                        };
                        let Some(integrity) = manifest
                            .get("dist")
                            .and_then(|dist| dist.get("integrity"))
                            .and_then(|integrity_value| integrity_value.as_str())
                        else {

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Re-run the command — a fresh process starts with an unpoisoned mutex
  2. Treat index.db as disposable cache: remove <store-dir>/index.db (it is rebuilt on demand) and retry
  3. Locate the FIRST panic message in the output; that panic, not the poisoning, is the bug to report upstream with a backtrace
  4. If reproducible on every run, capture with RUST_BACKTRACE=1 and file a pacquet issue including the store's index.db size
Defensive patterns

Strategy: try-catch

Try / catch

// Rust: recover from a poisoned StoreIndex lock instead of expect()-ing it
let guard = match index.lock() {
    Ok(g) => g,
    Err(poisoned) => {
        // index.db is a rebuildable cache; the guarded state can be re-derived
        poisoned.into_inner()
    }
};

Prevention

When it happens

Trigger: A worker thread hit an unwrap/expect/OOM abort while holding the StoreIndex lock during a concurrent read or update of index.db, and a later lock() in the cache-view path observed the poisoning.

Common situations: Latent pacquet bugs that panic inside a locked store-index section; memory pressure aborting a thread mid-critical-section; index.db corruption triggering a parse panic while locked.

Related errors


AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17). Data as JSON: /api/errors/1dbcd20a584fa623. Report an issue: GitHub.