openai / openai/codex

TUI visibly replays the entire transcript after deferred resize reflow on Windows Terminal

Open
#38,839 5 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI performance TUI windows-os
Dominant language
Rust
Stars
125k
Forks
19.5k
PR merge metrics
PR metrics pending

Description

Summary

In a long Codex CLI session, resizing or maximizing a Windows Terminal window can make the TUI visibly replay the entire transcript from the top before it returns to the latest output.

The same behavior can occur after returning to a terminal window that has been unfocused for a while: the first draw or key input consumes a previously deferred resize/reflow, the visible screen is cleared, and the conversation appears to stream from top to bottom again. The effect ranges from a brief flash to a long, CPU-heavy stall depending on transcript length.

This appears related to #22936, but the specific problem here is the non-atomic clear/replay sequence. It is not necessary to disable height-triggered transcript repair to avoid the visible replay.

Environment

  • Codex CLI: 0.147.0
  • OS: Windows 11, Microsoft Windows NT 10.0.26200.0 x64
  • Terminal: Windows Terminal 1.24.11911.0
  • Shells observed: native cmd.exe and PowerShell
  • Multiplexer: none
  • Source inspected: current main at 6efcdad4c3c167741ac3791766152c15b03e3653

codex doctor --json did not return within 30 seconds in this environment, so no doctor report is attached.

Steps to reproduce

  1. Open Codex CLI in a relatively small Windows Terminal window.
  2. Continue using one session until its transcript is long.
  3. Resize the terminal, switch to another window, and leave the Codex window unfocused for a while.
  4. Return to the Codex window and maximize/resize it, or start typing in the composer.
  5. Observe the terminal while the first post-focus draw is processed.

The delay in step 3 is not always required, but it makes the problem easier to observe when resize/reflow work remains pending until focus or input resumes drawing.

Expected behavior

Codex may need to rebuild terminal scrollback after a width or height change, but the user should see either the old complete frame or the new complete frame. The terminal should stay anchored at the latest output without exposing the intermediate clear and row-by-row transcript replay.

Actual behavior

The visible screen is cleared and the transcript is then visibly reconstructed from the beginning toward the bottom. On a large transcript this can look like the entire conversation is being loaded again and can cause a noticeable CPU stall.

Root-cause analysis

The behavior can be explained by the current resize-reflow path:

  1. A terminal resize schedules a debounced size recheck and transcript reflow.
  2. While the terminal is unfocused, the recheck/reflow can remain pending until a later draw.
  3. FocusGained schedules a draw, and a user turn can explicitly force an already-pending transcript reflow before processing the submission.
  4. handle_draw_size_change rebuilds the transcript when width reflow is required or terminal height changes.
  5. reflow_transcript_now renders the transcript and calls the terminal clear path immediately. In inline mode, clear_scrollback_and_visible_screen_ansi() writes and flushes CSI 2J/3J.
  6. The reflowed history rows are only queued at that point. They are flushed later by draw_with_resize_reflow, inside a synchronized update.

This splits the operation into two visible phases:

clear + flush
    ...time spent before/later draw...
synchronized viewport update + history replay + composer draw

The terminal can therefore render the cleared state and the subsequent replay instead of presenting one completed frame.

Simply removing height-only reflow does not look safe. #30745 documents a case where height-related source-backed replay restores rows lost after inline viewport changes. The reflow may be necessary; exposing its intermediate state is the avoidable part.

Proposed fix

Defer the destructive clear until draw_with_resize_reflow and execute all terminal mutations in the same synchronized update:

begin synchronized update
  clear visible screen / scrollback
  update inline viewport geometry
  flush all queued reflowed history rows
  redraw the composer and current frame
end synchronized update

A focused implementation can:

  1. Add a pending_transcript_replay_clear flag to Tui.
  2. Replace the immediate clear in transcript-rebuild paths with a method that only sets this flag and updates the internal viewport anchor.
  3. Consume the flag inside the existing stdout().sync_update(...) transaction in draw_with_resize_reflow.
  4. Clear the flag only after the synchronized transaction succeeds, so an I/O error leaves the operation retryable.
  5. Keep the existing width-, height-, stream-finalization-, and backtrack-triggered source repair semantics unchanged.

It is also useful to add debug-level timing events around resize/focus, overdue size rechecks, transcript rendering, and terminal replay. That distinguishes CPU time spent re-rendering Markdown from time spent writing rows to the terminal without logging transcript content.

Candidate implementation and validation

I prepared a focused candidate implementation for discussion:

Validation performed:

  • Added a regression test proving that transcript reflow queues both the terminal clear and replay rows, rather than clearing before the synchronized draw.
  • Existing height-shrink reflow behavior remains covered and unchanged.
  • just test -p codex-tui resize_reflow: 21/21 passed.
  • cargo clippy --tests -p codex-tui: passed with no diagnostics.
  • A full codex-tui test run passed 3580 tests and timed out on 4 unrelated startup/state tests. Three passed when rerun in isolation; one app-server startup/resume test continued to time out without touching the modified paths.

This is shared as analysis and a proposed approach in accordance with the invitation-only contribution policy; I am not opening an unsolicited pull request. I am happy to adjust or submit the patch if a maintainer confirms the approach and invites a PR.

Related issues

  • #22936: long conversations can jump the viewport back to the top on Windows Terminal/WSL
  • #30745: scrollback rows can disappear after inline viewport height changes and reappear after resize-backed replay
  • #21635: capped resize reflow can leave the resumed main view with only a partial transcript
  • #21945: transcript rendering/rebuild cost scales with session length
Adjacent TUI viewport reports
  • #14098: preserve viewport/context when exiting the transcript overlay
  • #10726: a closed Windows/WSL report where Plan mode could not retain the user's scroll position while idle
Similar symptoms on other Codex surfaces
  • #21834: the Windows desktop app jumps upward in long threads after submit
  • #22816: screen-reader focus in the app repeatedly returns to the beginning while output is generated

The last two reports involve the desktop app rather than the Rust CLI TUI and likely have a different renderer/root cause. They are included only to connect the shared user-visible “return to top / lose the latest position” symptom.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing handle_draw_size_change, reflow_transcript_now, and draw_with_resize_reflow in the codex-tui resize-reflow path. Run the existing resize_reflow tests, then add coverage for deferred clearing and replay during the synchronized draw. Done means resize and focus-triggered reflow preserve existing repair behavior without exposing an intermediate cleared screen.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.