xai-org/grok-build · error

terminal writer did not drain before suspend

Error message

terminal writer did not drain before suspend

What it means

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.

Source

Thrown at crates/codegen/xai-grok-pager/src/app/event_loop.rs:381

    input_paused: &std::sync::atomic::AtomicBool,
    reader_parked: &std::sync::atomic::AtomicBool,
    input_rx: &mut tokio::sync::mpsc::UnboundedReceiver<TimedInputEvent>,
    run_child: impl FnOnce(),
) -> std::io::Result<Option<(u16, u16)>> {
    use std::sync::atomic::Ordering;
    if !park_input_reader(input_paused, reader_parked, Duration::from_millis(500)) {
        input_paused.store(false, Ordering::Release);
        return Err(std::io::Error::new(
            std::io::ErrorKind::TimedOut,
            "terminal input reader did not park before suspend",
        ));
    }
    let writer_sync = terminal.backend_mut().writer_mut().writer_sync().clone();
    match writer_sync.wait_drained(Duration::from_millis(750)) {
        Ok(crate::render::draw::WriterDrain::Drained) => {}
        Ok(crate::render::draw::WriterDrain::TimedOut) => {
            input_paused.store(false, Ordering::Release);
            return Err(std::io::Error::new(
                std::io::ErrorKind::TimedOut,
                "terminal writer did not drain before suspend",
            ));
        }
        Err(error) => {
            input_paused.store(false, Ordering::Release);
            return Err(error);
        }
    }

    // Pre-child cursor probe (minimal only; minimal's startup already proved this terminal answers CPR)
    // Reader is parked, so the reply is ours
    let pre_cursor = screen_mode
        .is_minimal()
        .then(|| crossterm::cursor::position().ok())
        .flatten();
    // Fullscreen stays on the alternate screen
    // A full-screen child (editor / pager) draws over it directly, so the primary screen (the user's shell) never flashes while the child spawns

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Retry the suspend once the writer has caught up
  2. Reduce the amount of pending output before handoff (repaint minimally)
  3. Check tty/pty health and network latency to the terminal
  4. Increase the 750ms drain window for slow links
Defensive patterns

Strategy: retry

Try / catch

match suspend_for_child(...) {
  Err(e) if e.kind() == std::io::ErrorKind::TimedOut
      && e.to_string().contains("did not drain") => retry_after_flush(),
  other => other,
}

Prevention

When it happens

Trigger: 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.

Common situations: Opening an editor over a laggy SSH session; rendering a very large transcript right before editor handoff; pty buffer pressure.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/2857cc0743eb794e. Report an issue: GitHub.