Hmbown/CodeWhale · error

LSP request timed out for {method}

Error message

LSP request timed out for {method}

What it means

The request exceeded its per-call timeout window (timeout(wait)) without a reply and without the channel closing, so the pending entry is removed and the method name is reported. It indicates a slow or busy server rather than a dead one: startup indexing, serialized heavy requests, or a timeout budget too small for the method.

Source

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

            pending.remove(&id);
            return Err(err);
        }
        match timeout(wait, rx).await {
            Ok(Ok(reply)) => {
                if let Some(error) = reply.get("error") {
                    let message = error
                        .get("message")
                        .and_then(|v| v.as_str())
                        .unwrap_or("LSP request failed");
                    return Err(anyhow!("{message}"));
                }
                Ok(reply.get("result").cloned().unwrap_or(Value::Null))
            }
            Ok(Err(_)) => Err(anyhow!("LSP request channel closed")),
            Err(_) => {
                let mut pending = self.pending.lock().await;
                pending.remove(&id);
                Err(anyhow!("LSP request timed out for {method}"))
            }
        }
    }

    async fn shutdown(&self) {
        let mut child = self.child.lock().await;
        if let Some(mut c) = child.take() {
            let _ = c.start_kill();
            let _ = c.wait().await;
        }
    }
}

/// Send a JSON value as one Content-Length-framed JSON-RPC message.
async fn send_message(tx: &mpsc::Sender<Vec<u8>>, value: &Value) -> Result<()> {
    let body = serde_json::to_vec(value).context("serialize LSP message")?;
    let header = format!("Content-Length: {}\r\n\r\n", body.len());
    let mut frame = Vec::with_capacity(header.len() + body.len());

View on GitHub (pinned to 8880682c63)

Solutions

  1. Retry after the server finishes initializing/indexing (watch progress notifications or the server log)
  2. Increase the timeout budget for heavyweight methods, especially workspace-wide queries
  3. If timeouts repeat with no progress notifications, treat the server as hung and restart it
  4. Exclude vendor and build directories from server indexing to cut warm-up time

Example fix

// before: one short budget for every method
let reply = client.request(method, params).await; // timed out
// after: per-method budget
let wait = if method.starts_with("workspace/") {
    Duration::from_secs(120)
} else {
    Duration::from_secs(30)
};
Defensive patterns

Strategy: retry

Validate before calling

// Wait for server readiness (initialize done, indexing signal) before sending
use std::sync::atomic::{AtomicBool, Ordering};
fn server_ready(ready: &AtomicBool) -> bool {
    ready.load(Ordering::Acquire)
}

Try / catch

// Distinguish timeout from closure: retry timeouts with backoff, escalate closure
match client.request(method, params).await {
    Ok(reply) => Ok(reply),
    Err(e) if e.to_string().contains("timed out") => {
        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
        client.request(method, params).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Requests issued while the server is still indexing a large workspace, heavyweight calls like workspace/symbol over a monorepo, servers that process requests serially behind a long task, or per-method timeouts configured below real latency.

Common situations: The first minutes after opening a big project (rust-analyzer, tsserver, gopls warm-up), loaded CI machines, debugging adapters pausing the server, aggressive default timeouts.

Understand the failure class

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/8c05a9437ffc4748. Report an issue: GitHub.