astral-sh/ruff · warning

Received exit notification before a shutdown request

Error message

Received exit notification before a shutdown request

What it means

The client sent the `exit` notification without a prior `shutdown` request, violating the LSP lifecycle; the server returns an error from its main loop (main_loop.rs:68). The spec requires shutdown before exit, and the server exits with a non-zero status in this case.

Source

Thrown at crates/ty_server/src/server/main_loop.rs:68

                                    &req.method
                                );
                                client.respond_err(
                                    req.id,
                                    lsp_server::ResponseError {
                                        code: lsp_server::ErrorCode::InvalidRequest as i32,
                                        message: "Shutdown already requested".to_owned(),
                                        data: None,
                                    },
                                );
                                continue;
                            }

                            api::request(req)
                        }
                        Message::Notification(notification) => {
                            if notification.method == lsp_types::ExitNotification::METHOD.as_str() {
                                if !self.session.is_shutdown_requested() {
                                    return Err(anyhow!(
                                        "Received exit notification before a shutdown request"
                                    ));
                                }

                                tracing::debug!("Received exit notification, exiting");
                                return Ok(());
                            }

                            api::notification(notification)
                        }

                        // Handle the response from the client to a server request
                        Message::Response(response) => {
                            if let Some(handler) = self
                                .session
                                .request_queue_mut()
                                .outgoing_mut()
                                .complete(&response.id)

View on GitHub (pinned to 672bb4edf0)

Solutions

  1. Always send the `shutdown` request and await its response before sending `exit`
  2. In restart flows, shut down the old server before spawning the new one
  3. If using a client library, prefer its built-in lifecycle methods over manual messages

Example fix

// before
client.notify('exit');  // exit before shutdown -> error

// after
await client.sendRequest('shutdown');
client.notify('exit');
Defensive patterns

Strategy: fallback

Validate before calling

// TS: enforce ordering with a small lifecycle helper
let shutdownAcked = false;
await client.sendRequest('shutdown');
shutdownAcked = true;
client.notify('exit');

Try / catch

// TS: if exit raced ahead (e.g. timeout path), restart cleanly rather than reusing state
try { await stopServer(client); }
catch (e) {
  log.warn('server exited uncleanly; spawning a fresh instance');
  await startServer();
}

Prevention

When it happens

Trigger: A client that sends exit directly (skipping the shutdown request), a reconnect/restart routine that exits the old server without shutting it down, or a misimplemented lifecycle in a custom LSP client.

Common situations: Buggy or hand-rolled LSP client lifecycle code, editor extensions that 'restart server' by exiting only, or race where the shutdown request was still in flight when exit arrived.

Related errors


AI-assisted analysis of astral-sh/ruff@672bb4edf0 (2026-08-16). Data as JSON: /api/errors/0da3b6a6b569c6dc. Report an issue: GitHub.