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

  1. Call `LspClient::start` again to create a fresh client (and re-run initialize) before issuing further requests.
  2. Track client lifecycle state in your code; drop or replace the client after shutdown instead of reusing it.
  3. Guard concurrent use: shut down only when all in-flight requests are done, or hold the client behind an Arc/Mutex with explicit ownership.
  4. 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

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


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)