Hmbown/CodeWhale · warning · io::Error

editor handoff cancelled by pending terminal input

Error message

editor handoff cancelled by pending terminal input

What it means

During editor handoff, the TUI event loop spawns a child terminal for the editor. Before resuming, it checks there is no pending terminal input; if input is pending, it refuses the handoff with ErrorKind::Interrupted so keystrokes typed around the handoff are not lost or misrouted to the child.

Solutions

  1. Retry the editor handoff after the pending input drains.
  2. Dismiss or consume the pending keystrokes before opening the editor.
  3. Treat Interrupted as 'cancelled', keep the prompt state, and let the user reopen the editor.
Defensive patterns

Strategy: retry

Validate before calling

// ensure input quiescence before handoff
if pending_terminal_input().is_empty() { open_editor()?; }

Try / catch

match open_editor_handoff() {
    Err(e) if e.kind() == io::ErrorKind::Interrupted => schedule_retry_after_drain(),
    other => other,
}

Prevention

When it happens

Trigger: run_event_loop (called by run_tui) invokes the editor handoff while keys are still queued in the terminal input pump and resume_after_child_terminal is set up without a clean quiescent state.

Common situations: User launches the editor (e.g. multi-line prompt editor) while still typing or with buffered paste/key events arriving at the same moment.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1994f2e1c659db28. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/ui/event_loop.rs:6712

                    let seed = app.input.clone();
                    let editor_result = match terminal_input.pause_for_child_terminal().await {
                        Err(err) => Err(err),
                        Ok(()) => {
                            let result = prepare_terminal_input_handoff(
                                &terminal_input,
                                &mut pending_terminal_events,
                            )
                            .and_then(|ready| {
                                if ready {
                                    crate::tui::external_editor::spawn_editor_for_input(
                                        terminal,
                                        app.use_alt_screen(),
                                        app.use_mouse_capture,
                                        app.use_bracketed_paste,
                                        &seed,
                                    )
                                } else {
                                    Err(io::Error::new(
                                        io::ErrorKind::Interrupted,
                                        "editor handoff cancelled by pending terminal input",
                                    ))
                                }
                            });
                            terminal_input.resume_after_child_terminal();
                            force_terminal_repaint = true;
                            result
                        }
                    };
                    match editor_result {
                        Ok(crate::tui::external_editor::EditorOutcome::Edited(new)) => {
                            app.apply_external_edit(new);
                            let editor = std::env::var("VISUAL")
                                .ok()
                                .filter(|s| !s.trim().is_empty())
                                .or_else(|| {
                                    std::env::var("EDITOR")

View on GitHub (pinned to 73e0f67d83)