{"record":{"id":"0da3b6a6b569c6dc","repo":"astral-sh/ruff","slug":"received-exit-notification-before-a-shutdown-reque-0da3b6","errorCode":null,"errorMessage":"Received exit notification before a shutdown request","messagePattern":"Received exit notification before a shutdown request","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/ty_server/src/server/main_loop.rs","lineNumber":68,"sourceCode":"                                    &req.method\n                                );\n                                client.respond_err(\n                                    req.id,\n                                    lsp_server::ResponseError {\n                                        code: lsp_server::ErrorCode::InvalidRequest as i32,\n                                        message: \"Shutdown already requested\".to_owned(),\n                                        data: None,\n                                    },\n                                );\n                                continue;\n                            }\n\n                            api::request(req)\n                        }\n                        Message::Notification(notification) => {\n                            if notification.method == lsp_types::ExitNotification::METHOD.as_str() {\n                                if !self.session.is_shutdown_requested() {\n                                    return Err(anyhow!(\n                                        \"Received exit notification before a shutdown request\"\n                                    ));\n                                }\n\n                                tracing::debug!(\"Received exit notification, exiting\");\n                                return Ok(());\n                            }\n\n                            api::notification(notification)\n                        }\n\n                        // Handle the response from the client to a server request\n                        Message::Response(response) => {\n                            if let Some(handler) = self\n                                .session\n                                .request_queue_mut()\n                                .outgoing_mut()\n                                .complete(&response.id)","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/astral-sh/ruff/blob/5bb508f59481c1a02d5c7ff8568b9ffd8a763eef/crates/ty_server/src/server/main_loop.rs#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nsend(lsp_server::Notification::new(\"exit\".into(), null));\n// after\nlet id = send(lsp_server::Request::new(id_counter.next(), \"shutdown\".into(), ()));\nawait_response(id);\nsend(lsp_server::Notification::new(\"exit\".into(), null));","handlingStrategy":"fallback","validationCode":"// Track the shutdown handshake before exiting\nlet shutdownAcked = false;\nawait sendRequest('shutdown', null).then(() => { shutdownAcked = true; });\nif (!shutdownAcked) { log('shutdown request failed; still sending exit per fallback'); }\nsendNotification('exit');","typeGuard":null,"tryCatchPattern":"// Fallback wrapper for the exit path\nasync function shutdownAndExit() {\n  try {\n    await sendRequest('shutdown', null);\n  } catch (err) {\n    console.warn('shutdown request failed:', err);\n  } finally {\n    sendNotification('exit');\n  }\n}","preventionTips":["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"],"tags":["lsp","lifecycle","shutdown","protocol"],"backgroundTag":"lsp-shutdown-handshake-violation","analyzedSha":"5bb508f59481c1a02d5c7ff8568b9ffd8a763eef","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}