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

  1. Check that the configured LSP server binary runs and stays alive (run it manually in stdio mode and issue a request).
  2. Restart the LSP manager / client so a fresh server process is spawned, then retry the request.
  3. Check server logs and system logs (dmesg/journalctl) for crash, OOM, or signal termination of the server process.
  4. 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

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


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)