{"record":{"id":"61edd6baaacb08a6","repo":"BoundaryML/baml","slug":"lsp-client-sent-exit-before-completing-shutdown","errorCode":null,"errorMessage":"LSP client sent exit before completing shutdown","messagePattern":"LSP client sent exit before completing shutdown","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"baml_language/crates/baml_lsp_server/src/lib.rs","lineNumber":921,"sourceCode":"                    break;\n                }\n                SubmitResult::Closed => {\n                    terminate = true;\n                    break;\n                }\n            }\n        }\n        if terminate || stdio_closed.load(std::sync::atomic::Ordering::Acquire) {\n            break;\n        }\n    }\n\n    runtime.close_session(stdio_session);\n\n    if abnormal_exit {\n        // Lifecycle rule: `exit` before a completed shutdown is an abnormal\n        // termination (nonzero for stdio).\n        anyhow::bail!(\"LSP client sent exit before completing shutdown\");\n    }\n\n    tracing::info!(\"LSP server shutting down\");\n    Ok(())\n}\n\nfn absolutize_workspace_roots(workspace_roots: Vec<PathBuf>) -> anyhow::Result<Vec<PathBuf>> {\n    if workspace_roots.iter().all(|root| root.is_absolute()) {\n        return Ok(workspace_roots);\n    }\n\n    let cwd = std::env::current_dir().context(\"Failed to read current directory\")?;\n    Ok(workspace_roots\n        .into_iter()\n        .map(|root| {\n            if root.is_absolute() {\n                root\n            } else {","sourceCodeStart":903,"sourceCodeEnd":939,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/baml_lsp_server/src/lib.rs#L903-L939","documentation":"Raised in `run_stdio_loop` when the LSP client sends the `exit` notification before a `shutdown` request completed. Per the LSP lifecycle, `exit` after `shutdown` is clean; `exit` before completed shutdown is an abnormal termination, so the server bails with a nonzero exit for stdio.","triggerScenarios":"Client sends the `exit` notification while a `shutdown` request is still pending or was never sent; a client crash or hard kill issuing `exit` mid-shutdown; a client bug skipping the `shutdown` request.","commonSituations":"Editors killing the language server process on close instead of performing the full shutdown/exit handshake; flaky LSP client plugins that fire `exit` immediately; timeouts in the client causing it to abandon shutdown.","solutions":["Fix the client to send `shutdown` (request) and wait for its response before sending `exit`","If you control the editor plugin, implement the full LSP lifecycle: initialize → shutdown → exit","Check the client's shutdown timeout settings and increase if it aborts mid-shutdown","Inspect server logs to confirm `shutdown` was received before `exit`"],"exampleFix":"// before (client): fire-and-forget exit\nconnection.exit();\n\n// after (client): complete the lifecycle handshake\nlet _ = connection.request(\"shutdown\", null).await;\nconnection.notify(\"exit\");","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// client side: only send exit after shutdown resolved\nmatch shutdown_result {\n    Ok(_) => connection.notify(\"exit\"),\n    Err(e) => log::error!(\"shutdown failed, skipping exit: {e}\"),\n}","preventionTips":["Implement the full LSP lifecycle: initialize → shutdown (await response) → exit","Disable hard process kills on editor close in favor of the handshake","Increase client shutdown timeouts"],"tags":["lsp","rust","lifecycle","stdio"],"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-14T05:17:10.506Z"}