{"record":{"id":"39770d82e774a282","repo":"BoundaryML/baml","slug":"server-received-an-exit-notification-before-a-shutdown","errorCode":null,"errorMessage":"Server received an exit notification before a shutdown request was sent. Exiting...","messagePattern":"Server received an exit notification before a shutdown request was sent\\. Exiting\\.\\.\\.","errorType":"exception","errorClass":"anyhow","httpStatus":null,"severity":"warning","filePath":"engine/language_server/src/server/connection.rs","lineNumber":139,"sourceCode":"                            );\n                            self.sender.send(lsp::Message::Response(lsp::Response::new_err(\n                                id.clone(),\n                                lsp::ErrorCode::InvalidRequest as i32,\n                                \"Server received unexpected request while waiting for exit notification\".to_string(),\n                            )))?;\n                        }\n                        message => {\n                            tracing::warn!(\n                                \"Server received unexpected message while waiting for exit notification: {message:?}\"\n                            );\n                        }\n                    }\n                }\n            }\n            lsp::Message::Notification(lsp::Notification { method, .. })\n                if method == lsp_types::notification::Exit::METHOD =>\n            {\n                anyhow::bail!(\"Server received an exit notification before a shutdown request was sent. Exiting...\");\n            }\n            _ => Ok(false),\n        }\n    }\n\n    /// Join the I/O threads that underpin this connection.\n    /// This is guaranteed to be nearly immediate since\n    /// we close the only active channels to these threads prior\n    /// to joining them.\n    pub fn close(self) -> anyhow::Result<()> {\n        std::mem::drop(\n            Arc::into_inner(self.sender)\n                .expect(\"the client sender shouldn't have more than one strong reference\"),\n        );\n        std::mem::drop(self.receiver);\n        self.threads.into_iter().try_for_each(|t| t.join())?;\n        Ok(())\n    }","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/language_server/src/server/connection.rs#L121-L157","documentation":"Per the LSP specification, a client must send a 'shutdown' request before the 'exit' notification. If handle_shutdown sees an exit notification without a preceding shutdown request, it bails with this error to terminate the main loop, flagging a protocol violation by the client.","triggerScenarios":"The client sends notification 'exit' without first sending the 'shutdown' request — e.g. the editor process is killed, the extension crashes mid-shutdown, or a custom/test client omits shutdown.","commonSituations":"Editors force-killing the server on window close, flaky client extensions shutting down abruptly, integration tests that only send exit, or race conditions in client teardown logic.","solutions":["Ensure the client sends request 'shutdown' followed by notification 'exit' per LSP spec.","If abrupt exit is acceptable, treat exit-without-shutdown as a normal termination instead of an error in the server.","Check the client extension for code paths that skip shutdown (crash handlers, force-kill timers).","Log at info/warn level and exit cleanly rather than propagating an anyhow bail."],"exampleFix":"// before\nanyhow::bail!(\"Server received an exit notification before a shutdown request was sent. Exiting...\");\n// after\nlog::warn!(\"Client sent exit without shutdown; exiting.\");\nreturn Ok(true); // stop the event loop without error","handlingStrategy":"try-catch","validationCode":"// ensure notification payload matches the expected type before sending\nJSON.stringify(notification.params); // catch non-serializable payloads early","typeGuard":"function hasValidParams(n: { params?: unknown }): boolean {\n  return n.params !== undefined;\n}","tryCatchPattern":"match notification.extract::<N>(method) {\n    Ok(n) => handle(n),\n    Err(ExtractError::JsonError(e)) => log::error!(\"bad notification params: {e}\"),\n    Err(_) => unreachable!(),\n}","preventionTips":["Keep notification param structs Option-typed where fields are optional.","Version-check client and server so schemas stay aligned.","Fuzz/test notifications with representative client payloads.","Log the {json_err} detail for quick field-level debugging."],"tags":["lsp","shutdown","protocol"],"backgroundTag":"invalid-state-transition","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}