{"record":{"id":"2857cc0743eb794e","repo":"xai-org/grok-build","slug":"terminal-writer-did-not-drain-before-suspend","errorCode":null,"errorMessage":"terminal writer did not drain before suspend","messagePattern":"terminal writer did not drain before suspend","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-pager/src/app/event_loop.rs","lineNumber":381,"sourceCode":"    input_paused: &std::sync::atomic::AtomicBool,\n    reader_parked: &std::sync::atomic::AtomicBool,\n    input_rx: &mut tokio::sync::mpsc::UnboundedReceiver<TimedInputEvent>,\n    run_child: impl FnOnce(),\n) -> std::io::Result<Option<(u16, u16)>> {\n    use std::sync::atomic::Ordering;\n    if !park_input_reader(input_paused, reader_parked, Duration::from_millis(500)) {\n        input_paused.store(false, Ordering::Release);\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::TimedOut,\n            \"terminal input reader did not park before suspend\",\n        ));\n    }\n    let writer_sync = terminal.backend_mut().writer_mut().writer_sync().clone();\n    match writer_sync.wait_drained(Duration::from_millis(750)) {\n        Ok(crate::render::draw::WriterDrain::Drained) => {}\n        Ok(crate::render::draw::WriterDrain::TimedOut) => {\n            input_paused.store(false, Ordering::Release);\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::TimedOut,\n                \"terminal writer did not drain before suspend\",\n            ));\n        }\n        Err(error) => {\n            input_paused.store(false, Ordering::Release);\n            return Err(error);\n        }\n    }\n\n    // Pre-child cursor probe (minimal only; minimal's startup already proved this terminal answers CPR)\n    // Reader is parked, so the reply is ours\n    let pre_cursor = screen_mode\n        .is_minimal()\n        .then(|| crossterm::cursor::position().ok())\n        .flatten();\n    // Fullscreen stays on the alternate screen\n    // A full-screen child (editor / pager) draws over it directly, so the primary screen (the user's shell) never flashes while the child spawns","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-pager/src/app/event_loop.rs#L363-L399","documentation":"An io::Error of kind TimedOut from suspend_for_child when wait_drained(750ms) on the synchronous terminal writer returns WriterDrain::TimedOut — pending output bytes were not flushed to the tty before suspending the app for a child (e.g. an external editor). The input pause flag is restored and suspend is aborted so buffered output isn't interleaved with the child's screen.","triggerScenarios":"Large pending render output or a slow/blocked tty (SSH link, pty) preventing the writer from draining within 750 ms before a child process takes over the terminal.","commonSituations":"Opening an editor over a laggy SSH session; rendering a very large transcript right before editor handoff; pty buffer pressure.","solutions":["Retry the suspend once the writer has caught up","Reduce the amount of pending output before handoff (repaint minimally)","Check tty/pty health and network latency to the terminal","Increase the 750ms drain window for slow links"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match suspend_for_child(...) {\n  Err(e) if e.kind() == std::io::ErrorKind::TimedOut\n      && e.to_string().contains(\"did not drain\") => retry_after_flush(),\n  other => other,\n}","preventionTips":["Flush/minimize pending render output before editor handoff","Test suspend flows over slow SSH links","Allow a larger drain window on high-latency ttys"],"tags":["terminal","suspend","flush","timeout"],"backgroundTag":"terminal-suspend-timeout","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}