RightNow-AI/openfang · error
draw failed
Error message
draw failed
What it means
The init-wizard screen renders itself each loop iteration with `terminal.draw(...).expect("draw failed")`. Ratatui's `draw` returns `io::Result`; any failure writing the frame or querying terminal capabilities panics here. This wizard runs while background migration/copilot auth threads send events over channels, but the panic comes purely from terminal I/O, not from those channels.
Source
Thrown at crates/openfang-cli/src/tui/screens/init_wizard.rs:684
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
ratatui::restore();
original_hook(info);
}));
let mut terminal = ratatui::init();
let mut state = State::new();
let (test_tx, test_rx) = std::sync::mpsc::channel::<bool>();
let (migrate_tx, migrate_rx) =
std::sync::mpsc::channel::<Result<openfang_migrate::report::MigrationReport, String>>();
let (copilot_tx, copilot_rx) = std::sync::mpsc::channel::<Result<CopilotAuthEvent, String>>();
let result = loop {
terminal
.draw(|f| draw(f, f.area(), &mut state))
.expect("draw failed");
// Check for Copilot auth events
if state.step == Step::CopilotAuth {
while let Ok(event) = copilot_rx.try_recv() {
match event {
Ok(CopilotAuthEvent::DeviceCode {
user_code,
verification_uri,
}) => {
state.copilot_user_code = user_code;
state.copilot_verification_uri = verification_uri;
state.copilot_auth_status = CopilotAuthStatus::WaitingForUser;
}
Ok(CopilotAuthEvent::Authenticated) => {
state.copilot_auth_status = CopilotAuthStatus::FetchingModels;
}
Ok(CopilotAuthEvent::Models(models)) => {
state.copilot_auth_status = CopilotAuthStatus::Done;View on GitHub (pinned to acf2587e46)
Solutions
- Launch the wizard in a real interactive terminal without output redirection.
- Add a TTY guard before starting the wizard and print instructions instead of entering TUI mode.
- Handle draw errors with `?` and exit the wizard loop gracefully rather than panicking.
- Handle resize events and clamp/validate terminal size before drawing.
Example fix
// before
terminal
.draw(|f| draw(f, f.area(), &mut state))
.expect("draw failed");
// after
terminal
.draw(|f| draw(f, f.area(), &mut state))
.with_context(|| format!("wizard draw failed (terminal size {:?})", terminal.size().ok()))?; Defensive patterns
Strategy: fallback
Validate before calling
if !atty::is(atty::Stream::Stdout) {
eprintln!("init wizard needs an interactive terminal");
std::process::exit(1);
}
let (cols, rows) = terminal.size()? .into(); // reject zero-size
if cols == 0 || rows == 0 { eprintln!("terminal size invalid"); std::process::exit(1); } Try / catch
// propagate instead of expect
terminal.draw(|f| draw(f, f.area(), &mut state))
.context("wizard draw failed")?; Prevention
- Validate TTY and terminal dimensions before starting the wizard
- Gracefully exit the wizard loop on draw errors (restore terminal state in a guard)
- Avoid long blocking waits that outlive the terminal session without disconnect detection
- Test the wizard under piped output and CI to catch non-tty regressions
When it happens
Trigger: Rendering the wizard when stdout is not a TTY (redirected output), the terminal resizes to invalid/zero dimensions, the terminal is detached mid-wizard, or any underlying write/flush on the terminal backend fails.
Common situations: Piping the init wizard's output to a file, running it from scripts or CI, terminal emulators reporting zero-size after resize, or SSH sessions that drop during the (long-running) wizard.
Related errors
- Failed to draw
- Failed to draw
- MCP message too large: {content_length} bytes (max {MAX_MCP_
- Failed to set listener to non-blocking
AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02).
Data as JSON: /api/errors/894ac927ed6f9da8.
Report an issue: GitHub.