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
- Re-run the command — a fresh process starts with an unpoisoned mutex
- Treat index.db as disposable cache: remove <store-dir>/index.db (it is rebuilt on demand) and retry
- Locate the FIRST panic message in the output; that panic, not the poisoning, is the bug to report upstream with a backtrace
- 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
- Never unwrap/expect while holding the StoreIndex lock — return Results from locked sections
- Keep critical sections small so a panic elsewhere cannot poison the mutex
- Treat index.db as a disposable cache that can be deleted and rebuilt
- Run the CLI through a catch_unwind boundary that converts panics into exit codes and logs
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
- ERR_PNPM_DEDUPE_CHECK_ISSUES
- Not implemented
- unsupported operating system: {}
- registry returned 304 Not Modified unexpectedly
- registry returned 304 Not Modified unexpectedly
AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17).
Data as JSON: /api/errors/1dbcd20a584fa623.
Report an issue: GitHub.