rust-lang/rust-analyzer · error

client exited without proper shutdown sequence

Error message

client exited without proper shutdown sequence

What it means

rust-analyzer's main loop reads events from the LSP connection. When the event channel closes (client's stdio/socket ends) without the client first sending an `exit` notification after `shutdown`, the loop treats this as a protocol violation and aborts with this error instead of exiting cleanly. It enforces the LSP lifecycle: clients must not just drop the connection.

Source

Thrown at 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 e8f7e90aa3)

Solutions

  1. Fix the client/editor integration to send a `shutdown` request followed by an `exit` notification before closing stdio
  2. Check for client crashes: if the editor was killed, this error is expected fallout of the abrupt termination and can be ignored in logs
  3. If you manage rust-analyzer programmatically, terminate gracefully by writing a `shutdown`/`exit` pair, then close stdin
  4. Update rust-analyzer/client versions to match, since protocol mismatches between old clients and new servers can break the lifecycle

Example fix

// client-side, before closing the process
// before
child.stdin.close();
// after
send_request("shutdown", None);
send_notification("exit", None);
child.stdin.close();
Defensive patterns

Strategy: try-catch

Try / catch

// client side: treat this as expected on abrupt kill
match server.wait() {
    Err(e) if e.to_string().contains("without proper shutdown sequence") => {
        // client was killed uncleanly; log at info level, not error
    }
    other => other?,
}

Prevention

When it happens

Trigger: The client (editor) closes its end of the connection (closes stdin or kills the process pipe) while `run` is waiting on `next_event`, and no `shutdown` request + `exit` notification sequence was completed beforehand, so `next_event` returns `None`.

Common situations: Editor crashes or is force-killed; a wrapper script kills the server with SIGKILL instead of issuing an LSP shutdown; pipes are broken by the parent process exiting; misconfigured clients that terminate the transport abruptly.

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/64191cc27b43f324. Report an issue: GitHub.