TUI: completed modal view can remain mounted after paste-driven completion

Resolved 💬 4 comments Opened Mar 29, 2026 by iqdoctor Closed Apr 2, 2026

Summary

BottomPane::handle_paste() can leave a completed modal flow mounted while re-enabling the composer.

That creates an inconsistent TUI state: the composer becomes editable again, but rendering and key routing can still prefer stale view state because the completed flow was never torn down.

Current code

In codex-rs/tui/src/bottom_pane/mod.rs:

pub fn handle_paste(&mut self, pasted: String) {
    if let Some(view) = self.view_stack.last_mut() {
        let needs_redraw = view.handle_paste(pasted);
        if view.is_complete() {
            self.on_active_view_complete();
        }
        if needs_redraw {
            self.request_redraw();
        }
    } else {
        ...
    }
}

on_active_view_complete() re-enables the composer, but unlike the key-event completion path the paste path does not tear down the completed modal flow first.

Why this is a bug

The key-event path already treats view_complete as completion of the active modal flow:

  • tear down the completed flow from view_stack
  • then call on_active_view_complete()

The paste path only performs the second half.

That means the following invariants can be violated after a paste-driven completion:

  • stale view state can remain mounted in view_stack
  • composer input has already been re-enabled
  • rendering can still show stale active-view state
  • subsequent key routing can still prefer stale view state instead of the composer

This is especially easy to see when more than one view is stacked: even if the top view completed, the flow has not been torn down consistently before composer state is restored.

Minimal deterministic repro

A unit-level repro is straightforward:

  1. Create a BottomPane
  2. Disable composer input
  3. Push a lower blocking BottomPaneView
  4. Push a top BottomPaneView whose:
  • handle_paste() returns true
  • is_complete() returns true
  1. Call handle_paste("hello")

Expected:

  • the completed modal flow is torn down consistently with the key-event completion path
  • composer is re-enabled
  • next normal keypress goes to the composer

Actual:

  • composer is re-enabled
  • stale view state can remain mounted

Expected behavior

If a modal view completes during paste handling, Codex should tear down the completed modal flow before restoring composer state, just like the key-event completion path already does.

Additional context

I found this while investigating TUI/input freeze behavior in the CLI. The separate input-stream recovery bug is tracked in #12645; this issue is specifically about paste-driven modal teardown semantics.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗