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

  1. Find and fix the original panic — this expect is only the symptom; run with RUST_BACKTRACE=1 to see the first unwinding thread
  2. Restart the process (or drop and recreate the LspRegistry) — poisoning cannot be cleared on a live Mutex
  3. 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
  4. 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

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


AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18). Data as JSON: /api/errors/5c94de8d4ae77b9c. Report an issue: GitHub.