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
- Detect broken-pipe/EIO and exit the TUI loop gracefully (set should_quit) instead of panicking.
- Verify a TTY exists before entering run_chat_tui (atty/IsTerminal check) and fall back to non-TUI mode.
- Handle SIGHUP/SIGTERM and terminal close events to quit the loop proactively.
- 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
- Check std::io::stdout().is_terminal() before starting TUI mode; offer a non-TUI fallback.
- Handle SIGINT/SIGHUP so the loop exits cleanly on session loss.
- Write draw failures to a log file (stderr may be gone too).
- Test TUI under SSH disconnect and piped-stdout scenarios.
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
- Failed to draw
- draw failed
- MCP message too large: {content_length} bytes (max {MAX_MCP_
- Failed to listen for SIGTERM
- Failed to build HTTP client
AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02).
Data as JSON: /api/errors/5e43423ecc5e556f.
Report an issue: GitHub.