{"record":{"id":"fbb133cd50a5d468","repo":"astral-sh/ruff","slug":"client-exited-without-proper-shutdown-sequence-fbb133","errorCode":null,"errorMessage":"client exited without proper shutdown sequence","messagePattern":"client exited without proper shutdown sequence","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/ty_server/src/server/main_loop.rs","lineNumber":26,"sourceCode":"use lsp_types::Notification;\nuse lsp_types::Uri;\n\npub(crate) type ConnectionSender = crossbeam::channel::Sender<Message>;\npub(crate) type MainLoopSender = crossbeam::channel::Sender<Event>;\npub(crate) type MainLoopReceiver = crossbeam::channel::Receiver<Event>;\n\nimpl Server {\n    pub(super) fn main_loop(&mut self) -> crate::Result<()> {\n        self.initialize(&Client::new(\n            self.main_loop_sender.clone(),\n            self.connection.sender.clone(),\n        ));\n\n        let mut scheduler = Scheduler::new(self.worker_threads);\n\n        while let Ok(next_event) = self.next_event() {\n            let Some(next_event) = next_event else {\n                anyhow::bail!(\"client exited without proper shutdown sequence\");\n            };\n\n            let client = Client::new(\n                self.main_loop_sender.clone(),\n                self.connection.sender.clone(),\n            );\n\n            match next_event {\n                Event::Message(msg) => {\n                    let Some(msg) = self.session.should_defer_message(msg) else {\n                        continue;\n                    };\n\n                    let task = match msg {\n                        Message::Request(req) => {\n                            self.session\n                                .request_queue_mut()\n                                .incoming_mut()","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/astral-sh/ruff/blob/672bb4edf04c84f8b0753346359daee4057158f2/crates/ty_server/src/server/main_loop.rs#L8-L44","documentation":"The server's main loop read EOF from the client channel (stdin closed) without ever receiving the `exit` notification, so the server bails with this error instead of shutting down cleanly (main_loop.rs:26). Per the LSP spec, a server whose connection drops before `exit` terminates with an error status.","triggerScenarios":"The editor or client process is killed (SIGKILL, crash, force-quit), stdin is closed without shutdown/exit, or a client library terminates the child without the LSP shutdown sequence.","commonSituations":"Killing the editor instead of quitting it, test harnesses that dispose the connection abruptly, process supervisors recycling the server, or timeouts that hard-kill the language server.","solutions":["If killing the server intentionally, treat this exit as expected — otherwise close with shutdown request + exit notification","In client code, use the library's graceful dispose (e.g. LanguageClient.stop()) rather than child.kill()","Check whether the editor crashed; this error is a symptom, not the cause"],"exampleFix":"// before\nserverProcess.kill();  // stdin drops -> 'client exited without proper shutdown sequence'\n\n// after\nawait client.sendRequest('shutdown');\nclient.notify('exit');\nawait serverProcess.exited;","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// TS: treat abrupt-EOF as a benign abnormal exit when you killed the server\nserverProc.on('exit', (code) => {\n  if (intentionalKill && code !== 0) return; // expected: no shutdown sequence\n  handleUnexpectedExit(code);\n});","preventionTips":["Always stop servers via shutdown request + exit notification, even on cleanup paths","Use the client library's dispose()/stop() instead of child.kill()","Distinguish intentional kills from crashes before alerting on non-zero exit codes"],"tags":["lsp","shutdown","lifecycle","process","stdio"],"backgroundTag":null,"analyzedSha":"672bb4edf04c84f8b0753346359daee4057158f2","analyzedAt":"2026-08-16T08:54:05.464Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}