{"record":{"id":"af77311ed3897277","repo":"Hmbown/CodeWhale","slug":"lsp-outbound-channel-closed","errorCode":null,"errorMessage":"LSP outbound channel closed","messagePattern":"LSP outbound channel closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/lsp/client.rs","lineNumber":530,"sourceCode":"            let _ = c.wait().await;\n        }\n        for task in &self.tasks {\n            task.abort();\n        }\n        self.pending.lock().await.clear();\n    }\n}\n\n/// Send a JSON value as one Content-Length-framed JSON-RPC message.\nasync fn send_message(tx: &mpsc::Sender<Vec<u8>>, value: &Value) -> Result<()> {\n    let body = serde_json::to_vec(value).context(\"serialize LSP message\")?;\n    let header = format!(\"Content-Length: {}\\r\\n\\r\\n\", body.len());\n    let mut frame = Vec::with_capacity(header.len() + body.len());\n    frame.extend_from_slice(header.as_bytes());\n    frame.extend_from_slice(&body);\n    tx.send(frame)\n        .await\n        .map_err(|_| anyhow!(\"LSP outbound channel closed\"))?;\n    Ok(())\n}\n\n/// Background task that drains the outbound queue and writes each frame to\n/// the LSP server's stdin. Exits cleanly when the channel closes.\nasync fn writer_task(mut stdin: tokio::process::ChildStdin, mut rx: mpsc::Receiver<Vec<u8>>) {\n    while let Some(frame) = rx.recv().await {\n        if stdin.write_all(&frame).await.is_err() {\n            break;\n        }\n        if stdin.flush().await.is_err() {\n            break;\n        }\n    }\n}\n\n/// Background task that parses `Content-Length`-framed JSON-RPC frames from\n/// the LSP server's stdout. Pushes each parsed JSON value to `tx`. Exits","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/lsp/client.rs#L512-L548","documentation":"send_message frames an LSP JSON-RPC message and pushes it onto the mpsc channel consumed by writer_task, which writes frames to the LSP server's stdin. The oneshot-style failure means the receiving end (writer_task / the writer task) is gone, so the server process has exited or the client was shut down. Any queued or in-flight LSP request will fail with this error once the outbound channel has no receiver.","triggerScenarios":"Calling client.request, send via request(), open_or_change (document open/change notifications), or spawn_with_timeout paths after the writer_task has exited because the LSP server process died, stdin closed, or the client was dropped/shut down.","commonSituations":"Language server crashed (OOM, panic, unsupported workspace), server was killed by a timeout, user closed the editor session while background document sync was still sending didOpen/didChange notifications, or a race between shutdown and a queued turn in tests like queued_ordinary_turn_does_not_multiply_engine_goal_continuations.","solutions":["Check whether the LSP server process is still alive before sending; restart the client (spawn a fresh server) when the channel is closed.","Inspect the server's stderr logs to find why the server exited (bad initialization options, missing binary, version incompatibility).","Guard shutdown ordering: cancel or drain pending requests and stop document notifications before dropping the client.","Re-queue in-flight document open/change notifications after a client restart so editor state stays in sync."],"exampleFix":"// before\nclient.request(method, params).await?;\n// after\nif !client.is_running() {\n    client.restart().await?;\n}\nclient.request(method, params).await.map_err(|e| {\n    eprintln!(\"LSP send failed, restarting server: {e}\");\n    client.restart().await?;\n    client.request(method, params).await\n})?;","handlingStrategy":"try-catch","validationCode":"// Rust-ish guard\nif !lsp_client.is_running() {\n    lsp_client = lsp_client.restart().await?;\n}","typeGuard":"fn can_send(client: &LspClient) -> bool { client.is_running() && client.server_alive() }","tryCatchPattern":"match client.request(method, params).await {\n    Err(e) if e.to_string().contains(\"LSP outbound channel closed\") => {\n        client.restart().await?;\n        client.request(method, params).await\n    }\n    other => other,\n}","preventionTips":["Check server liveness before each document-sync burst","Cancel in-flight notifications before client shutdown","Monitor server stderr for early crash signals","Re-open documents after any client restart"],"tags":["lsp","ipc","channel-closed","process-lifecycle"],"backgroundTag":"broken-pipe","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}