Kuberwastaken/claurst · error · anyhow::Error
LSP client already shut down
Error message
LSP client already shut down
What it means
`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.
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.
Example fix
// before client.shutdown().await?; let hov = client.hover(uri, pos).await?; // Err: already shut down // after client.shutdown().await?; let client = LspClient::start(config).await?; client.initialize().await?; let hov = client.hover(uri, pos).await?;
Defensive patterns
Strategy: type-guard
Validate before calling
struct LiveClient(LspClient);
impl LiveClient {
fn shutdown(self) -> LiveClientDropped { self.client.shutdown().await; LiveClientDropped }
}
// only LiveClient exposes request methods; shutdown consumes it Type guard
fn is_usable(client: &LspClient) -> bool {
// writer is private; approximate by tracking shutdown in your wrapper
!shutdown_handles.lock().unwrap().contains(&client_id(client))
} Try / catch
match client.hover(&uri, pos).await {
Err(e) if e.to_string().contains("already shut down") => {
let mut c = clients.lock().await;
*c = Some(LspClient::start(config.clone()).await?);
c.as_ref().unwrap().hover(&uri, pos).await?
}
other => other?,
} Prevention
- 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.
When it happens
Trigger: Calling any request method (hover, definition, references, document_symbols, initialize, or a second shutdown) on an LspClient after `shutdown()` has completed.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Failed to start LSP server
- LSP server stdin not available
- LSP server stdout not available
- LSP request ' ' timed out (server: )
- LSP request ' ' channel closed (server: )
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/7d4dd02e8c711c17.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/lsp.rs:309
params: serde_json::Value,
) -> anyhow::Result<serde_json::Value> {
let id = self.next_id();
let msg = json!({
"jsonrpc": "2.0",
"id": id,
"method": method,
"params": params,
});
let body = serde_json::to_string(&msg)?;
let (tx, rx) = oneshot::channel();
self.pending.insert(id, tx);
{
let writer = self
.writer
.as_ref()
.ok_or_else(|| anyhow::anyhow!("LSP client already shut down"))?;
let mut w = writer.lock().await;
send_message(&mut w, &body).await?;
}
let response =
tokio::time::timeout(std::time::Duration::from_secs(30), rx)
.await
.map_err(|_| {
anyhow::anyhow!(
"LSP request '{}' timed out (server: {})",
method,
self.server_name
)
})?
.map_err(|_| {
anyhow::anyhow!(
"LSP request '{}' channel closed (server: {})",
method,
View on GitHub (pinned to b0637c97ec)