Inline TUI history inserts use region scrolls that xterm.js deletes — permanent transcript gaps in VS Code terminal and other xterm.js hosts
What version of Codex CLI is running?
0.139.0
What subscription do you have?
N/A — rendering bug, independent of subscription
Which model were you using?
N/A — model-independent (TUI rendering)
What platform is your computer?
macOS (Darwin 25.5.0, arm64)
What terminal emulator and version are you using (if applicable)?
Any xterm.js-based terminal. Reproduced in:
- VS Code integrated terminal (xterm.js)
- Our own Electron/Tauri terminal embedding xterm.js (CodePress Terminal)
Does not reproduce in iTerm2 or kitty.
What issue are you seeing?
Codex's inline TUI pushes finished transcript lines "into scrollback" with a scroll-region sequence:
ESC[1;{B}r set scroll region rows 1..B (rows below B hold the composer/status area)
ESC[{k}S scroll the region up k lines
ESC[r reset the scroll region
ESC[{C};1H ESC[J ... repaint from row C down (inside a DEC 2026 synchronized-output block)
On iTerm2/kitty, lines scrolled off the top of a row-1-anchored region are appended to scrollback, so this works as intended. xterm.js, however, only adds lines to scrollback on full-screen scrolls (scrollTop=0 AND scrollBottom=rows−1); region scrolls delete the lines outright.
Codex believes those lines are safely in terminal history and never repaints them, so the transcript develops permanent gaps in xterm.js hosts. A window resize "repairs" the damage because it forces Codex's full re-render — which is the smoking gun that the content was lost by the emulator, not by Codex's own state.
We root-caused this from per-tab raw PTY captures: a paragraph that vanished from the buffer disappeared at exactly such a region-scroll frame, with zero ED2/ED3/RIS anywhere before it in the stream.
What steps can reproduce the bug?
With Codex:
- Open a VS Code integrated terminal (or any xterm.js-based terminal).
- Run
codex(default inline TUI; no flags needed). - Have a conversation long enough that finished transcript lines get pushed out of the live region.
- Scroll up: earlier transcript lines are missing (gaps), not in scrollback.
- Resize the window: the missing content reappears (full re-render).
Standalone repro of the emulator divergence (no Codex needed):
seq 1 100; printf '\e[1;10r\e[5S\e[r'
Scroll up afterwards: in iTerm2/kitty the 5 lines that left the top of the region are in scrollback; in VS Code's integrated terminal they are deleted.
What is the expected behavior?
Transcript lines pushed out of the live region should survive in the terminal's scrollback in xterm.js-based terminals, the same as they do in iTerm2/kitty.
Suggested fix: when inserting history lines on the normal buffer, emit a full-screen scroll instead of a region scroll — reset the region, move the cursor to the bottom row, emit k linefeeds, then repaint the composer/status rows (all inside the existing DEC 2026 block). xterm.js records full-screen scrolls in scrollback, and the follow-up repaint Codex already issues covers the shifted rows.
Additional information
We ship a Codex-detecting rewrite at the PTY→xterm.js boundary in our terminal that performs exactly this transform:
ESC[1;{B}r ESC[{k}S ESC[r → ESC[r ESC[999;1H \n×k ESC[H
(cursor ends at home either way, matching DECSTBM semantics). Verified by replaying a captured Codex session through @xterm/headless with and without the rewrite: 46 history lines rescued, 0 of 35 final visible screen rows differed — i.e. the final screen is byte-identical because Codex's own synchronized repaint covers the rows shifted by the full-screen scroll. That patch works as a downstream mitigation, but every xterm.js embedder (VS Code included) hits this, so it seems worth fixing at the source.
Related but distinct: #14277 and xtermjs/xterm.js#5745 concern --no-alt-screen plus ED2/ED3 purges; this issue is the default inline TUI's history-insert path. Arguably xterm.js could also start scrollback-pushing row-1-anchored region scrolls (matching iTerm2/kitty), but a Codex-side fix reaches users much faster than an xterm.js + VS Code + embedder upgrade chain.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗