{"record":{"id":"8c05a9437ffc4748","repo":"Hmbown/CodeWhale","slug":"lsp-request-timed-out-for-method","errorCode":null,"errorMessage":"LSP request timed out for {method}","messagePattern":"LSP request timed out for (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/lsp/client.rs","lineNumber":333,"sourceCode":"            pending.remove(&id);\n            return Err(err);\n        }\n        match timeout(wait, rx).await {\n            Ok(Ok(reply)) => {\n                if let Some(error) = reply.get(\"error\") {\n                    let message = error\n                        .get(\"message\")\n                        .and_then(|v| v.as_str())\n                        .unwrap_or(\"LSP request failed\");\n                    return Err(anyhow!(\"{message}\"));\n                }\n                Ok(reply.get(\"result\").cloned().unwrap_or(Value::Null))\n            }\n            Ok(Err(_)) => Err(anyhow!(\"LSP request channel closed\")),\n            Err(_) => {\n                let mut pending = self.pending.lock().await;\n                pending.remove(&id);\n                Err(anyhow!(\"LSP request timed out for {method}\"))\n            }\n        }\n    }\n\n    async fn shutdown(&self) {\n        let mut child = self.child.lock().await;\n        if let Some(mut c) = child.take() {\n            let _ = c.start_kill();\n            let _ = c.wait().await;\n        }\n    }\n}\n\n/// Send a JSON value as one Content-Length-framed JSON-RPC message.\nasync fn send_message(tx: &mpsc::Sender<Vec<u8>>, value: &Value) -> Result<()> {\n    let body = serde_json::to_vec(value).context(\"serialize LSP message\")?;\n    let header = format!(\"Content-Length: {}\\r\\n\\r\\n\", body.len());\n    let mut frame = Vec::with_capacity(header.len() + body.len());","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/lsp/client.rs#L315-L351","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: one short budget for every method\nlet reply = client.request(method, params).await; // timed out\n// after: per-method budget\nlet wait = if method.starts_with(\"workspace/\") {\n    Duration::from_secs(120)\n} else {\n    Duration::from_secs(30)\n};","handlingStrategy":"retry","validationCode":"// Wait for server readiness (initialize done, indexing signal) before sending\nuse std::sync::atomic::{AtomicBool, Ordering};\nfn server_ready(ready: &AtomicBool) -> bool {\n    ready.load(Ordering::Acquire)\n}","typeGuard":null,"tryCatchPattern":"// Distinguish timeout from closure: retry timeouts with backoff, escalate closure\nmatch client.request(method, params).await {\n    Ok(reply) => Ok(reply),\n    Err(e) if e.to_string().contains(\"timed out\") => {\n        tokio::time::sleep(std::time::Duration::from_secs(2)).await;\n        client.request(method, params).await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["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"],"tags":["lsp","timeout","performance","client"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}