rust-lang/rust · warning · anyhow::Error

client exited without proper shutdown sequence

Error message

client exited without proper shutdown sequence

What it means

Bailed by the main event loop when next_event returns None — the LSP client closed the connection without sending the Exit notification that the loop uses as its normal termination signal. rust-analyzer treats an abrupt close as abnormal because it cannot distinguish it from a crashed client.

Source

Thrown at src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs:205

                .map(|f| format!("**/{f}"));
            self.register_did_save_capability(additional_patterns);
        }

        if self.config.discover_workspace_config().is_none() {
            self.fetch_workspaces_queue.request_op(
                "startup".to_owned(),
                FetchWorkspaceRequest { path: None, force_crate_graph_reload: false },
            );
            if let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) =
                self.fetch_workspaces_queue.should_start_op()
            {
                self.fetch_workspaces(cause, path, force_crate_graph_reload);
            }
        }

        while let Ok(event) = self.next_event(&inbox) {
            let Some(event) = event else {
                anyhow::bail!("client exited without proper shutdown sequence");
            };
            if matches!(
                &event,
                Event::Lsp(lsp_server::Message::Notification(Notification { method, .. }))
                if method == lsp_types::ExitNotification::METHOD.as_str()
            ) {
                return Ok(());
            }
            self.handle_event(event);
        }

        Err(anyhow::anyhow!("A receiver has been dropped, something panicked!"))
    }

    fn register_did_save_capability(&mut self, additional_patterns: impl Iterator<Item = String>) {
        let additional_filters = additional_patterns.map(|pattern| {
            lsp_types::DocumentFilter::TextDocumentFilter(lsp_types::TextDocumentFilter::Pattern(
                lsp_types::TextDocumentFilterPattern {

View on GitHub (pinned to 7088e4b63a)

Solutions

  1. On the client side, always send the `exit` notification after `shutdown` completes, per the LSP spec.
  2. If wrapping r-a in a proxy, forward the exit notification faithfully.
  3. Treat this message in your logs as a client/transport issue, not a rust-analyzer bug — it is r-a reporting the protocol violation.

Example fix

// before: bail on a clean EOF
let Some(event) = event else {
    anyhow::bail!("client exited without proper shutdown sequence");
};

// after: log and exit cleanly (a missing exit is the client's fault, not fatal for r-a)
let Some(event) = event else {
    tracing::warn!("client disconnected without sending `exit`; shutting down");
    return Ok(());
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Client-side: always perform the LSP shutdown handshake before disconnecting:
async fn shutdown_graceful(server: &LspClient) -> Result<(), Box<dyn std::error::Error>> {
    let _ = server.request("shutdown").await?;
    server.notify("exit").await?;
    server.close().await?;
    Ok(())
}

Type guard

null

Try / catch

// Server-side: treat a clean EOF as a normal exit rather than an error:
let Some(event) = event else {
    tracing::warn!("client disconnected without `exit`; treating as shutdown");
    return Ok(());
};

Prevention

When it happens

Trigger: The LSP transport's receiver hits EOF (client disconnected) before an Exit notification arrives. Happens when the editor kills the server process, the client crashes, or a transport proxy drops the connection.

Common situations: Editor force-killed during shutdown; a buggy client that closes stdin without sending exit; running r-a behind a wrapper that swallowed the exit notification; OS-level process kill.

Related errors


AI-assisted analysis of rust-lang/rust@7088e4b63a (2026-08-10). Data as JSON: /api/errors/354000bebf9d735b. Report an issue: GitHub.