RightNow-AI/openfang · error

Failed to draw

Error message

Failed to draw

What it means

ratatui's Terminal::draw() returns io::Result<CompletedFrame> and errors when writing to the terminal backend fails — e.g. stdout closed, not a TTY, resize/IO errors, or the terminal was disconnected. run_chat_tui panics via expect, aborting the TUI mid-loop.

Source

Thrown at crates/openfang-cli/src/tui/chat_runner.rs:792

    // Store the requested agent name for later resolution
    if let Some(ref name) = agent_name {
        state.agent_name = name.clone();
    }

    // Boot sequence: check for daemon, or boot kernel in-process
    if let Some(base_url) = crate::find_daemon() {
        state.resolve_daemon_agent(&base_url, agent_name.as_deref());
    } else {
        state.booting = true;
        event::spawn_kernel_boot(config, tx);
    }

    // ── Main loop ────────────────────────────────────────────────────────────
    while !state.should_quit {
        terminal
            .draw(|frame| state.draw(frame))
            .expect("Failed to draw");

        match rx.recv_timeout(Duration::from_millis(33)) {
            Ok(ev) => state.handle_event(ev),
            Err(mpsc::RecvTimeoutError::Timeout) => {}
            Err(mpsc::RecvTimeoutError::Disconnected) => break,
        }
        // Drain queued events
        while let Ok(ev) = rx.try_recv() {
            state.handle_event(ev);
        }
    }

    ratatui::restore();
}

View on GitHub (pinned to acf2587e46)

Solutions

  1. Detect broken-pipe/EIO and exit the TUI loop gracefully (set should_quit) instead of panicking.
  2. Verify a TTY exists before entering run_chat_tui (atty/IsTerminal check) and fall back to non-TUI mode.
  3. Handle SIGHUP/SIGTERM and terminal close events to quit the loop proactively.
  4. Propagate the io::Error from run_chat_tui and print a user-friendly message.

Example fix

// before
terminal
    .draw(|frame| state.draw(frame))
    .expect("Failed to draw");
// after
if terminal.draw(|frame| state.draw(frame)).is_err() {
    break; // terminal gone; exit loop gracefully
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure stdout is a TTY before entering the TUI
use std::io::IsTerminal;
fn tui_supported() -> bool {
    std::io::stdout().is_terminal()
}

Try / catch

// break the loop on draw failure instead of panicking
match terminal.draw(|frame| state.draw(frame)) {
    Ok(_) => {}
    Err(e) => {
        eprintln!("terminal unavailable: {e}; exiting TUI");
        break;
    }
}

Prevention

When it happens

Trigger: terminal.draw(|frame| state.draw(frame)) fails because stdout was closed/piped away after startup, the terminal backend write errored (EIO), or the process lost its controlling TTY while the chat TUI main loop is running.

Common situations: User closed the terminal or disconnected an SSH session while the TUI ran; running under a wrapper that kills the pty; stdout redirected to a file mid-session; broken pipe from a parent process.

Related errors


AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02). Data as JSON: /api/errors/5e43423ecc5e556f. Report an issue: GitHub.