{"record":{"id":"7d4dd02e8c711c17","repo":"Kuberwastaken/claurst","slug":"lsp-client-already-shut-down","errorCode":null,"errorMessage":"LSP client already shut down","messagePattern":"LSP client already shut down","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-rust/crates/core/src/lsp.rs","lineNumber":309,"sourceCode":"        params: serde_json::Value,\r\n    ) -> anyhow::Result<serde_json::Value> {\r\n        let id = self.next_id();\r\n        let msg = json!({\r\n            \"jsonrpc\": \"2.0\",\r\n            \"id\": id,\r\n            \"method\": method,\r\n            \"params\": params,\r\n        });\r\n        let body = serde_json::to_string(&msg)?;\r\n\r\n        let (tx, rx) = oneshot::channel();\r\n        self.pending.insert(id, tx);\r\n\r\n        {\r\n            let writer = self\r\n                .writer\r\n                .as_ref()\r\n                .ok_or_else(|| anyhow::anyhow!(\"LSP client already shut down\"))?;\r\n            let mut w = writer.lock().await;\r\n            send_message(&mut w, &body).await?;\r\n        }\r\n\r\n        let response =\r\n            tokio::time::timeout(std::time::Duration::from_secs(30), rx)\r\n                .await\r\n                .map_err(|_| {\r\n                    anyhow::anyhow!(\r\n                        \"LSP request '{}' timed out (server: {})\",\r\n                        method,\r\n                        self.server_name\r\n                    )\r\n                })?\r\n                .map_err(|_| {\r\n                    anyhow::anyhow!(\r\n                        \"LSP request '{}' channel closed (server: {})\",\r\n                        method,\r","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/core/src/lsp.rs#L291-L327","documentation":"`send_request_inner` (backing initialize, hover, definition, references, document_symbols, shutdown) found `self.writer` is `None`, meaning `shutdown()` was already called on this LspClient and the child process/stdin were dropped. Requests cannot be sent to a client after shutdown; this is a use-after-shutdown state error.","triggerScenarios":"Calling any request method (hover, definition, references, document_symbols, initialize, or a second shutdown) on an LspClient after `shutdown()` has completed.","commonSituations":"Keeping an LspClient in a long-lived cache after shutdown and reusing it; races where one task shuts the client down while another still serves requests; retry logic re-using a stale client handle.","solutions":["Call `LspClient::start` again to create a fresh client (and re-run initialize) before issuing further requests.","Track client lifecycle state in your code; drop or replace the client after shutdown instead of reusing it.","Guard concurrent use: shut down only when all in-flight requests are done, or hold the client behind an Arc/Mutex with explicit ownership.","Check shutdown paths (app exit, error handling) that may shut down clients other code still references."],"exampleFix":"// before\nclient.shutdown().await?;\nlet hov = client.hover(uri, pos).await?; // Err: already shut down\n// after\nclient.shutdown().await?;\nlet client = LspClient::start(config).await?;\nclient.initialize().await?;\nlet hov = client.hover(uri, pos).await?;","handlingStrategy":"type-guard","validationCode":"struct LiveClient(LspClient);\nimpl LiveClient {\n    fn shutdown(self) -> LiveClientDropped { self.client.shutdown().await; LiveClientDropped }\n}\n// only LiveClient exposes request methods; shutdown consumes it","typeGuard":"fn is_usable(client: &LspClient) -> bool {\n    // writer is private; approximate by tracking shutdown in your wrapper\n    !shutdown_handles.lock().unwrap().contains(&client_id(client))\n}","tryCatchPattern":"match client.hover(&uri, pos).await {\n    Err(e) if e.to_string().contains(\"already shut down\") => {\n        let mut c = clients.lock().await;\n        *c = Some(LspClient::start(config.clone()).await?);\n        c.as_ref().unwrap().hover(&uri, pos).await?\n    }\n    other => other?,\n}","preventionTips":["Model the lifecycle in types: consume the client on shutdown so stale handles can't be used.","Serialize access to the client; don't shut down while other tasks may still send requests.","Replace cached clients with fresh instances instead of reusing after shutdown.","On app exit, shut down clients last, after all request work completes."],"tags":["lsp","lifecycle","use-after-shutdown","rust"],"backgroundTag":"invalid-state-transition","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}