astral-sh/ruff · warning
client exited without proper shutdown sequence
Error message
client exited without proper shutdown sequence
What it means
The LSP server's event loop (`main_loop`) polls `next_event()`, which returns `Ok(None)` when the message stream ends. If the stream ends without the client sending a proper `exit` notification after `shutdown`, the loop treats this as a protocol violation and aborts with this anyhow bail instead of exiting cleanly. LSP requires clients to follow the `initialize` -> `shutdown` -> `exit` sequence.
Solutions
- Ensure your LSP client sends a `shutdown` request and then an `exit` notification before closing the connection
- If you control the client, handle editor/process termination by sending the LSP shutdown/exit sequence in a cleanup handler
- Treat this message in server logs as a symptom of abrupt client disconnect, not an internal Ruff bug; restart the server if needed
Example fix
// custom LSP client cleanup // before process::exit(0); // after client.shutdown().await?; client.exit().await; // sends the LSP exit notification process::exit(0);
Defensive patterns
Strategy: try-catch
Try / catch
match server_exit_result { Err(e) if e.to_string().contains("proper shutdown sequence") => log.warn("client disconnected without LSP shutdown/exit"), other => other? } Prevention
- Always send the LSP shutdown request followed by the exit notification before terminating the client
- Register cleanup handlers that close the LSP connection gracefully on editor shutdown
- Avoid force-killing the editor process while language servers are attached
When it happens
Trigger: The LSP client (editor process) terminates abruptly or closes the stdio/IPC stream without sending the `shutdown` request followed by the `exit` notification; `next_event()` returns None on stream EOF before an Exit event was processed.
Common situations: Editor crashes or is force-killed while the Ruff language server is running; a custom or scripted LSP client that stops reading/writing and drops the connection; debugging sessions where the transport is closed manually.
Related errors
- Received exit notification before a shutdown request
- Failed to send notification
- Received exit notification before a shutdown request
- -32603
- Attempted to add edits for a document that was already…
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/91c5ee77385c09e0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/ruff_server/src/server/main_loop.rs:29
server::{api, schedule},
session::Client,
};
pub(crate) type MainLoopSender = crossbeam::channel::Sender<Event>;
pub(crate) type MainLoopReceiver = crossbeam::channel::Receiver<Event>;
impl Server {
pub(super) fn main_loop(&mut self) -> crate::Result<()> {
self.initialize(&Client::new(
self.main_loop_sender.clone(),
self.connection.sender.clone(),
));
let mut scheduler = schedule::Scheduler::new(self.worker_threads);
while let Ok(next_event) = self.next_event() {
let Some(next_event) = next_event else {
anyhow::bail!("client exited without proper shutdown sequence");
};
match next_event {
Event::Message(msg) => {
let client = Client::new(
self.main_loop_sender.clone(),
self.connection.sender.clone(),
);
let task = match msg {
Message::Request(req) => {
self.session
.request_queue_mut()
.incoming_mut()
.register(req.id.clone(), req.method.clone());
if self.session.is_shutdown_requested() {
tracing::warn!(View on GitHub (pinned to 26f38c119c)