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

Resolved 💬 4 comments Opened Mar 29, 2026 by iqdoctor Closed Apr 2, 2026
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

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 ↗

4 Comments

iqdoctor · 5 months ago

I updated the fix branch after review. For this issue, the relevant bottom_pane/mod.rs change now keeps the paste-driven completion path aligned with the existing key-driven completion semantics: when a paste-driven view completes, the bottom pane tears down the completed modal flow before re-enabling the composer.

I also strengthened the regression coverage so this path is exercised with a stacked-view case, which would have exposed the stale-view variant directly.

The branch is here:
https://github.com/openai/codex/compare/main...strato-space:tui-input-recovery

This issue is distinct from the input-stream recovery bug tracked in #12645. The same branch currently carries both hunks, but for this issue the bottom_pane/mod.rs change is the relevant part.

etraut-openai contributor · 5 months ago

@iqdoctor, thanks for your analysis. I've put up a PR (#16202) that includes a simpler fix than the one you've proposed. Feel free to review and/or build it locally to confirm that it fixes your repro case.

etraut-openai contributor · 4 months ago

This will be fixed in the next release.

ybratec · 4 months ago

Follow-up on the earlier question about validating the fix: I do not have a clean manual paste-only repro beyond the deterministic code-path / unit-level repro from the original report.

I did hit a related stale-TUI symptom today on current v0.118.0 (Linux + tmux), but it appears to be a different path than #16192.

In the new incident, the child subagent completed, the parent thread emitted its final assistant message, and the rollout log for thread 019d61b9-5b70-78e2-9a11-56ebcfd12bba recorded task_complete at 2026-04-06T10:55:05.491Z, so the backend turn completed normally.

The user-visible symptom was that the tmux TUI still looked hung/stale after switching back from the subagent to the main agent via /agent.

So I cannot use this current incident as evidence that #16202 regressed or failed to fix the original paste-driven completion path here. This looks adjacent, but more like a thread-switch / focus / input-state bug in the same general TUI family, closer to #12645 / #11472 symptom class than to the original paste path in this issue.