astral-sh/ruff · error · anyhow::Error
Received exit notification before a shutdown request
Error message
Received exit notification before a shutdown request
What it means
The LSP lifecycle mandates that the client send a `shutdown` request before the `exit` notification. ty's main loop enforces this: if `exit` arrives while `session.is_shutdown_requested()` is false, it returns this error, terminating the server with a failure exit code. This mirrors rust-analyzer's behavior and is intended to surface clients that violate the LSP shutdown handshake.
Solutions
- Send a `shutdown` request and await its response before sending `exit` (standard LSP lifecycle)
- If the client died unexpectedly, restart the language server cleanly - the error is expected to kill the process
- In test harnesses/scripts, add the shutdown step before exit
- Report the client integration if it systematically skips shutdown
Example fix
// before
send(lsp_server::Notification::new("exit".into(), null));
// after
let id = send(lsp_server::Request::new(id_counter.next(), "shutdown".into(), ()));
await_response(id);
send(lsp_server::Notification::new("exit".into(), null)); Defensive patterns
Strategy: fallback
Validate before calling
// Track the shutdown handshake before exiting
let shutdownAcked = false;
await sendRequest('shutdown', null).then(() => { shutdownAcked = true; });
if (!shutdownAcked) { log('shutdown request failed; still sending exit per fallback'); }
sendNotification('exit'); Try / catch
// Fallback wrapper for the exit path
async function shutdownAndExit() {
try {
await sendRequest('shutdown', null);
} catch (err) {
console.warn('shutdown request failed:', err);
} finally {
sendNotification('exit');
}
} Prevention
- Always implement the full LSP lifecycle: initialize -> shutdown -> exit
- Never kill the server process while it is running; close the transport only after `exit`
- In test harnesses, include the shutdown step in teardown
- If the server exits with this message, check the client's exit path for a skipped or lost shutdown request
When it happens
Trigger: The client sends an `exit` notification (or closes the transport) without previously sending a successful `shutdown` request - e.g. killing the editor abruptly, a transport failure that drops the shutdown request, or a client implementation skipping the handshake.
Common situations: Users force-quitting an editor or crashing the LSP client; client integrations that terminate the server process directly; automated test harnesses that send `exit` immediately after startup; a lost/failed shutdown request over a flaky pipe or socket.
Related errors
- client exited without proper shutdown sequence
- Received exit notification before a shutdown request
- client exited without proper shutdown sequence
- Failed to send notification
- Tried to close unavailable document
AI-assisted analysis of astral-sh/ruff@5bb508f594 (2026-09-05).
Data as JSON: /api/errors/0da3b6a6b569c6dc.
Report an issue: GitHub.
Appendix: 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 5bb508f594)