{"record":{"id":"c625c643c374e3ac","repo":"xai-org/grok-build","slug":"terminal-writer-thread-exited","errorCode":null,"errorMessage":"terminal writer thread exited","messagePattern":"terminal writer thread exited","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-pager-render/src/render/draw.rs","lineNumber":223,"sourceCode":"    /// Shared writer progress used by the suspend path to\n    /// [`WriterSync::wait_drained`] before a child takes the tty.\n    pub fn writer_sync(&self) -> &WriterSync {\n        &self.sync\n    }\n}\nimpl Write for TermWriter {\n    fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {\n        self.buf.extend_from_slice(data);\n        Ok(data.len())\n    }\n    fn flush(&mut self) -> std::io::Result<()> {\n        if self.buf.is_empty() {\n            return Ok(());\n        }\n        let sequence = self.sync.reserve_sequence();\n        let data = std::mem::take(&mut self.buf);\n        if self.tx.send(WriterPayload { sequence, data }).is_err() {\n            let error = std::io::Error::new(\n                std::io::ErrorKind::BrokenPipe,\n                \"terminal writer thread exited\",\n            );\n            self.sync\n                .mark_failed(std::io::Error::new(error.kind(), error.to_string()));\n            return Err(error);\n        }\n        Ok(())\n    }\n}\nimpl Drop for TermWriter {\n    fn drop(&mut self) {\n        let _ = self.flush();\n        self.sync.writer_active.store(false, Ordering::Release);\n    }\n}\n/// Handle for the background writer thread.\n///","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-pager-render/src/render/draw.rs#L205-L241","documentation":"During flush, the buffered terminal output is sent to a dedicated writer thread via a channel. If that thread has exited, send fails and a BrokenPipe io::Error with this message is produced; the failure is also recorded via mark_failed so sequence accounting stays consistent. Callers see flush fail rather than silently losing output.","triggerScenarios":"Calling flush (or dropping the writer via Drop, or write_payload) after the background writer thread panicked, returned, or was joined during teardown.","commonSituations":"Terminal teardown races where Drop runs while the writer thread is already gone; writer thread panic from an io error on stderr; double-shutdown of the renderer.","solutions":["Ensure the writer/pager is not used after shutdown; drop order should end the writer last","Check writer-thread logs for a panic or stderr write failure and fix the root cause","Re-create the renderer/terminal session if continued output is required","Handle the BrokenPipe from flush as terminal and stop drawing"],"exampleFix":"// before\nterminal.flush()?; // BrokenPipe if writer thread already exited\n// after\nmatch terminal.flush() {\n    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => { /* session torn down */ }\n    other => other?,\n}","handlingStrategy":"try-catch","validationCode":"// before flushing, ensure the writer thread is still alive\nif !terminal.writer_thread_alive() {\n    eprintln!(\"terminal writer thread already exited; skipping flush\");\n    return;\n}","typeGuard":null,"tryCatchPattern":"match terminal.flush() {\n    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {\n        // writer thread gone; tear down and stop drawing\n    }\n    other => other?,\n}","preventionTips":["Keep the writer thread alive until after the final flush/drop","Join or signal the writer thread only during explicit shutdown","Catch panics in the writer thread so it never exits silently","Avoid using the terminal renderer after teardown begins"],"tags":["terminal","threading","io","render"],"backgroundTag":"broken-pipe","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}