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

  1. Launch the wizard in a real interactive terminal without output redirection.
  2. Add a TTY guard before starting the wizard and print instructions instead of entering TUI mode.
  3. Handle draw errors with `?` and exit the wizard loop gracefully rather than panicking.
  4. 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

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


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