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
- Retry after the server finishes initializing/indexing (watch progress notifications or the server log)
- Increase the timeout budget for heavyweight methods, especially workspace-wide queries
- If timeouts repeat with no progress notifications, treat the server as hung and restart it
- 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
- Send requests only after initialize and a readiness signal
- Use per-method timeouts: generous for workspace-wide queries, short for interactive ones
- Respect progress notifications instead of hammering a busy server
- Cap retries with backoff; repeated timeouts mean restart, not another retry
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- {message}
- ${detail || ("HTTP " + res.status)}
- No active turn
- ${response.status} ${response.statusText}
- SSE stream idle timeout after {}s — no data received (bytes_
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/8c05a9437ffc4748.
Report an issue: GitHub.