Hmbown/CodeWhale · error

LSP request channel closed

Error message

LSP request channel closed

What it means

request registers a oneshot reply channel in the pending map, writes the JSON-RPC request to the server's stdin, and awaits the reply. This error means the reply channel's sender was dropped before a response arrived — the server-side reader/dispatch task ended (or the client is shutting down) while this request was in flight.

Solutions

  1. Verify the language-server process is alive (check exit status / stderr) and restart the client if it died
  2. Avoid issuing requests while the client is shutting down; check a healthy/ready flag first
  3. Capture server stderr logs to find why the reader task ended
  4. Retry the request after reinitialization

Example fix

// before
let reply = client.request("textDocument/hover", params, wait).await?;
// after
if client.is_shutdown() {
    anyhow::bail!("LSP client shut down; reinitialize before requesting");
}
let reply = client.request("textDocument/hover", params, wait).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the server process is alive and initialized before requesting
if !client.is_alive().await || !client.is_initialized().await {
    anyhow::bail!("LSP server not ready; skipping request");
}

Try / catch

match client.request(method, params, wait).await {
    Ok(reply) => reply,
    Err(e) if e.to_string() == "LSP request channel closed" => {
        // server died mid-request: log, restart, optionally retry once
        client.restart().await?;
        client.request(method, params, wait).await
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Language-server process exited so the reader task that resolves pending ids terminated; client shutdown dropped all pending senders; the reader task panicked or was aborted while requests were outstanding.

Common situations: LSP binary crashed mid-session (OOM, panic in server); server killed by shutdown while a hover/completion request was pending; misconfigured command that dies after start.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/97141d6ff4a8ab21. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/lsp/client.rs:485

            let id = *next;
            *next = next.saturating_add(1);
            id
        };
        let (tx, rx) = oneshot::channel();
        {
            let mut pending = self.pending.lock().await;
            pending.insert(id, tx);
        }
        let payload = json!({
            "jsonrpc": "2.0",
            "id": id,
            "method": method,
            "params": params,
        });
        // The deadline includes queue backpressure, not just the reply.
        let response = timeout(wait, async {
            send_message(&self.tx_outbound, &payload).await?;
            rx.await.map_err(|_| anyhow!("LSP request channel closed"))
        })
        .await;
        self.pending.lock().await.remove(&id);
        match response {
            Ok(Ok(reply)) => {
                if let Some(error) = reply.get("error") {
                    let message = error
                        .get("message")
                        .and_then(Value::as_str)
                        .unwrap_or("LSP request failed");
                    return Err(anyhow!("{message}"));
                }
                reply
                    .get("result")
                    .cloned()
                    .ok_or_else(|| anyhow!("LSP response has no result"))
            }
            Ok(Err(error)) => Err(error),

View on GitHub (pinned to 73e0f67d83)