{"record":{"id":"1dbcd20a584fa623","repo":"pnpm/pnpm","slug":"store-index-mutex","errorCode":null,"errorMessage":"store index mutex","messagePattern":"store index mutex","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"pnpm/crates/cli/src/cli_args/cache.rs","lineNumber":207,"sourceCode":"                        meta_file_paths\n                            .push((path_str.replace('\\\\', \"/\"), entry.path().to_path_buf()));\n                    }\n                }\n                meta_file_paths.sort();\n\n                // IndexMap preserves insertion order so the JSON key order is\n                // deterministic (driven by the sorted file paths), matching\n                // pnpm's plain-object output.\n                let mut meta_files_by_path = IndexMap::new();\n\n                // pnpm's cacheView opens a writable StoreIndex that creates\n                // index.db when absent, so a fresh/empty store reports every\n                // version as non-cached rather than erroring. `shared_readonly_in`\n                // returns None when index.db does not exist, which we treat the\n                // same way: every lookup is a miss.\n                let store_index = StoreIndex::shared_readonly_in(&config.store_dir);\n                let store_index =\n                    store_index.as_ref().map(|index| index.lock().expect(\"store index mutex\"));\n\n                for (file_path, full_path) in meta_file_paths {\n                    let Some(meta_object) = load_meta(&full_path) else { continue };\n                    let mtime = fs::metadata(&full_path).and_then(|meta| meta.modified()).ok();\n\n                    let mut cached_versions = Vec::new();\n                    let mut non_cached_versions = Vec::new();\n\n                    for (version, json_frag) in meta_object.versions.fragments() {\n                        let Ok(manifest) = serde_json::from_str::<serde_json::Value>(&json_frag)\n                        else {\n                            continue;\n                        };\n                        let Some(integrity) = manifest\n                            .get(\"dist\")\n                            .and_then(|dist| dist.get(\"integrity\"))\n                            .and_then(|integrity_value| integrity_value.as_str())\n                        else {","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/pnpm/pnpm/blob/6261b7f388016d57ca6b90340342411cd1d0d00f/pnpm/crates/cli/src/cli_args/cache.rs#L189-L225","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rust: recover from a poisoned StoreIndex lock instead of expect()-ing it\nlet guard = match index.lock() {\n    Ok(g) => g,\n    Err(poisoned) => {\n        // index.db is a rebuildable cache; the guarded state can be re-derived\n        poisoned.into_inner()\n    }\n};","preventionTips":["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"],"tags":["rust","mutex","concurrency","store-index","cache","pacquet"],"backgroundTag":"mutex-poisoned","analyzedSha":"6261b7f388016d57ca6b90340342411cd1d0d00f","analyzedAt":"2026-08-17T18:30:54.750Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}