{"record":{"id":"f24b9f37e641cdf4","repo":"Hmbown/CodeWhale","slug":"lsp-diagnostics-channel-closed-before-publishdiagnostics","errorCode":null,"errorMessage":"LSP diagnostics channel closed before publishDiagnostics","messagePattern":"LSP diagnostics channel closed before publishDiagnostics","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/lsp/client.rs","lineNumber":406,"sourceCode":"        let path_buf = path.to_path_buf();\n        let (_, version) = timeout(\n            deadline.saturating_duration_since(tokio::time::Instant::now()),\n            self.open_or_change(path, text),\n        )\n        .await\n        .map_err(|_| anyhow!(\"LSP diagnostics timed out sending document\"))??;\n        loop {\n            let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());\n            if remaining.is_zero() {\n                return Err(anyhow!(\n                    \"LSP diagnostics timed out before a current publication\"\n                ));\n            }\n            let mut rx = self.diagnostics_rx.lock().await;\n            let (file, published_version, items) = match timeout(remaining, rx.recv()).await {\n                Ok(Some(item)) => item,\n                Ok(None) => {\n                    return Err(anyhow!(\n                        \"LSP diagnostics channel closed before publishDiagnostics\"\n                    ));\n                }\n                Err(_) => {\n                    return Err(anyhow!(\n                        \"LSP diagnostics timed out before a current publication\"\n                    ));\n                }\n            };\n            if file != path_buf || published_version.is_some_and(|published| published != version) {\n                continue;\n            }\n            return Ok(DiagnosticPublication {\n                items,\n                document_version: Some(version),\n                diagnostic_version: published_version,\n            });\n        }","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/lsp/client.rs#L388-L424","documentation":"The mpsc receiver on the diagnostics channel returned None, meaning all senders were dropped — the LSP server task (or the client internals that own the sender) has shut down before delivering publishDiagnostics. The library treats this as an error rather than an empty diagnostic result, because a closed channel is a shutdown signal, not 'no problems found'.","triggerScenarios":"The language-server child process exited or the reader task ended while diagnostics_for was waiting on rx.recv(); the client was shut down concurrently with a diagnostics request; server crashed mid-session.","commonSituations":"LSP binary path misconfigured so the server exits immediately; server crashed on a malformed document; shutdown/drop racing an in-flight diagnostics wait in tests or during editor close.","solutions":["Check the language-server command/path starts correctly and the process stays alive (capture stderr logs)","Guard against concurrent shutdown — don't call diagnostics_for after dropping the client or during shutdown","Reinitialize/restart the LSP client and retry the request","If this happens in tests, ensure the server stub actually emits publishDiagnostics before exiting"],"exampleFix":"// before\nlet diags = client.diagnostics_for(path, text, wait).await?; // Err: channel closed\n// after\nif !client.is_alive().await {\n    client.restart().await?;\n}\nlet diags = client.diagnostics_for(path, text, wait).await?;","handlingStrategy":"try-catch","validationCode":"// check client/server health before waiting on diagnostics\nif !client.is_alive().await {\n    anyhow::bail!(\"LSP server not running; restart before requesting diagnostics\");\n}","typeGuard":null,"tryCatchPattern":"match client.diagnostics_for(path, text, wait).await {\n    Ok(d) => d,\n    Err(e) if e.to_string().contains(\"channel closed\") => {\n        client.restart().await?;\n        client.diagnostics_for(path, text, wait).await\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never call diagnostics_for after initiating client shutdown","Validate the language-server command runs at all (config smoke test at startup)","Capture child stderr to catch early server crashes","Treat channel-closed as a restart signal, not empty diagnostics"],"tags":["lsp","channel-closed","process-crash"],"backgroundTag":"broken-pipe","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}