Kuberwastaken/claurst · error · anyhow::Error
LSP request ' ' channel closed (server: )
Error message
LSP request '{}' channel closed (server: {}) What it means
Thrown in `LspClient::send_request_inner` when the oneshot channel carrying the JSON-RPC response is dropped before a reply arrives. The reader task (`dispatch_incoming`) resolves pending requests; if it exits (server stdout closed, server crashed) it drops all pending senders, so `rx` yields `RecvError`. This means the language server died or closed its stdout while a request was in flight, distinct from a 30s timeout.
Solutions
- Check that the configured LSP server binary runs and stays alive (run it manually in stdio mode and issue a request).
- Restart the LSP manager / client so a fresh server process is spawned, then retry the request.
- Check server logs and system logs (dmesg/journalctl) for crash, OOM, or signal termination of the server process.
- Verify the server version is compatible with the request params sent (e.g. capabilities declared in initialize).
Example fix
// before: fire-and-forget call with no handling of dead server
let hover = manager.hover(path, root, line, col).await?;
// after: detect dead server, restart, retry once
let hover = match manager.hover(path, root, line, col).await {
Ok(h) => h,
Err(e) if e.to_string().contains("channel closed") => {
manager.shutdown_all().await;
manager.hover(path, root, line, col).await?
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: retry
Validate before calling
// Verify the server binary exists before starting requests
if !which(config.command).is_ok() { return Err("server binary not found"); } Type guard
fn client_alive(client: &LspClient) -> bool { client.writer.is_some() } Try / catch
match manager.hover(path, root, line, col).await {
Ok(h) => h,
Err(e) if e.to_string().contains("channel closed") => {
manager.shutdown_all().await; // respawn and retry once
manager.hover(path, root, line, col).await.unwrap_or(None)
}
Err(_) => None,
} Prevention
- Pin and smoke-test the language server binary version in your environment setup.
- Wrap feature calls with a restart-and-retry helper that detects dead-server errors.
- Monitor server stderr/stdout logging for early exit causes (OOM, panics).
When it happens
Trigger: Any request via send_request_inner (initialize, hover, definition, references, document_symbols, shutdown) where the server process exits or its stdout pipe closes after the request was written but before the response is read.
Common situations: LSP server binary segfaults or panics mid-request; server exits due to OOM kill; user's tooling killed the child process; server binary version mismatch causing an early exit; stdin closure triggers server shutdown while a request is pending.
Related errors
- LSP server closed stdout
- LSP message missing Content-Length header
- Failed to start LSP server
- LSP server stdin not available
- LSP server stdout not available
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/a9b2857066ed095d.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/lsp.rs:325
.writer
.as_ref()
.ok_or_else(|| anyhow::anyhow!("LSP client already shut down"))?;
let mut w = writer.lock().await;
send_message(&mut w, &body).await?;
}
let response =
tokio::time::timeout(std::time::Duration::from_secs(30), rx)
.await
.map_err(|_| {
anyhow::anyhow!(
"LSP request '{}' timed out (server: {})",
method,
self.server_name
)
})?
.map_err(|_| {
anyhow::anyhow!(
"LSP request '{}' channel closed (server: {})",
method,
self.server_name
)
})?;
if let Some(err) = response.get("error") {
return Err(anyhow::anyhow!(
"LSP error from {}: {}",
self.server_name,
err
));
}
Ok(response["result"].clone())
}
/// Send a JSON-RPC notification (fire-and-forget, no response expected).
async fn send_notification_inner(
View on GitHub (pinned to b0637c97ec)