TUI: completed modal view can remain mounted after paste-driven completion
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:
- Create a
BottomPane - Disable composer input
- Push a lower blocking
BottomPaneView - Push a top
BottomPaneViewwhose:
handle_paste()returnstrueis_complete()returnstrue
- 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.
4 Comments
I updated the fix branch after review. For this issue, the relevant
bottom_pane/mod.rschange 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.rschange is the relevant part.@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.
This will be fixed in the next release.
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-56ebcfd12bbarecordedtask_completeat2026-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
#16202regressed 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/#11472symptom class than to the original paste path in this issue.