ultraworkers/claw-code · critical
lsp registry lock poisoned
Error message
lsp registry lock poisoned
What it means
Panic from the LSP registry's diagnostics-recording method (the push/record entry at lsp_client.rs:185-193): the std::sync::Mutex guarding the servers map is poisoned because some thread panicked while holding it, and .expect("lsp registry lock poisoned") then unwinds in every subsequent caller. Rust mutexes poison on panic-by-holder, and the poison is sticky for the rest of the process — the registry is unusable until restart.
Source
Thrown at rust/crates/runtime/src/lsp_client.rs:197
/// Add diagnostics to a server.
pub fn add_diagnostics(
&self,
language: &str,
diagnostics: Vec<LspDiagnostic>,
) -> Result<(), String> {
let mut inner = self.inner.lock().expect("lsp registry lock poisoned");
let server = inner
.servers
.get_mut(language)
.ok_or_else(|| format!("LSP server not found for language: {language}"))?;
server.diagnostics.extend(diagnostics);
Ok(())
}
/// Get diagnostics for a specific file path.
pub fn get_diagnostics(&self, path: &str) -> Vec<LspDiagnostic> {
let inner = self.inner.lock().expect("lsp registry lock poisoned");
inner
.servers
.values()
.flat_map(|s| &s.diagnostics)
.filter(|d| d.path == path)
.cloned()
.collect()
}
/// Clear diagnostics for a language server.
pub fn clear_diagnostics(&self, language: &str) -> Result<(), String> {
let mut inner = self.inner.lock().expect("lsp registry lock poisoned");
let server = inner
.servers
.get_mut(language)
.ok_or_else(|| format!("LSP server not found for language: {language}"))?;
server.diagnostics.clear();
Ok(())View on GitHub (pinned to 08106b0c37)
Solutions
- Find and fix the original panic — this expect is only the symptom; run with RUST_BACKTRACE=1 to see the first unwinding thread
- Restart the process (or drop and recreate the LspRegistry) — poisoning cannot be cleared on a live Mutex
- If you maintain this code, recover instead of dying: self.inner.lock().unwrap_or_else(|p| p.into_inner()) — the guarded data (a plain HashMap) has no broken invariants after a panic
- Audit every panic site (unwrap/expect/indexing) reachable while the lock is held in lsp_client.rs and convert them to Result-returning errors
Example fix
// before (runtime/src/lsp_client.rs:186) — cascade panic on poison
let mut inner = self.inner.lock().expect("lsp registry lock poisoned");
// after — recover the (structurally valid) map from the PoisonError
let mut inner = self
.inner
.lock()
.unwrap_or_else(|poison| poison.into_inner()); Defensive patterns
Strategy: try-catch
Try / catch
// record/push diagnostics can panic only via lock poisoning; contain it
let recorded = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
registry.push_diagnostics(language, diagnostics.clone())
}));
if recorded.is_err() {
// registry lock is poisoned: rebuild the LspRegistry rather than reuse it
tracing::error!("lsp registry poisoned; recreating");
} Prevention
- Never allow panics (unwrap/expect/index slicing) inside code that holds the LSP registry lock — convert to Result errors
- Parse language-server output outside the critical section; lock only for the map mutation
- Treat one 'lsp registry lock poisoned' panic as fatal for that registry: rebuild it instead of calling more methods on it
When it happens
Trigger: Calling registry.record/push of diagnostics (extends server.diagnostics for a language) after any earlier panic occurred inside a critical section of this registry — e.g. a panic in server spawn, serialization of LspDiagnostic, or a .expect elsewhere in lsp_client.rs while the lock was held. Typical trigger order: thread A panics holding the lock in any method (dispatch, register, disconnect), thread B then calls this method and dies here.
Common situations: A malformed LSP server response causes an unwrap panic inside a locked section; a spawned language server crashes and the error path panics while the registry is locked; fuzz/load tests that intentionally panic in one thread and then reuse the shared LspRegistry in another.
Related errors
- mcp registry lock poisoned
- registry lock poisoned
- team registry lock poisoned
- cron registry lock poisoned
- worker registry lock poisoned
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/5c94de8d4ae77b9c.
Report an issue: GitHub.