Inline TUI history inserts use region scrolls that xterm.js deletes — permanent transcript gaps in VS Code terminal and other xterm.js hosts

Open 💬 3 comments Opened Jun 11, 2026 by lightninglu10

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:

  1. Open a VS Code integrated terminal (or any xterm.js-based terminal).
  2. Run codex (default inline TUI; no flags needed).
  3. Have a conversation long enough that finished transcript lines get pushed out of the live region.
  4. Scroll up: earlier transcript lines are missing (gaps), not in scrollback.
  5. 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.

View original on GitHub ↗

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