[BUG] Fullscreen history view intermittently omits entries when scrolling back down after a deep scroll-up
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (closest are #56546 fullscreen janky scrolling and #72215 fullscreen scrolling broken; neither describes entries vanishing with the layout closed up)
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
In fullscreen mode, after wheel-scrolling far up past the top of the visible history and riding back down, the re-rendered view intermittently omits one or more consecutive entries. Two flavors:
- Closed-up omission (most common): the entries above and below the missing ones are seamed together with no gap. Frozen example:
"✻ Crunched for 5m 40s"flows directly into"● Both tracks are in flight..."while the five entries between them (a background-command completion, an assistant message, a tool summary, two more completions) are simply not rendered. - Blank band: an empty region of rows where a tool batch should render, painted content above and below.
Scrolling another tick sometimes repairs the view. A sticky variant survives small scrolls in both directions and repairs only after ctrl+End followed by scrolling back up - at which point every missing entry renders correctly, so the history model still holds the content; the scrolled-window emission skipped it.
What Should Happen?
Every history entry should render at every scroll position; entries between two visible neighbors should never be omitted or replaced by blank rows.
Error Messages/Logs
N/A - silent rendering omission, no error output.
Steps to Reproduce
- Fullscreen mode (
"tui": "fullscreen"), Windows, a session with a few hundred KB of TUI output. - Wheel-scroll up 150+ notches, past the top of the loaded history.
- Ride back down in ~15-notch steps, inspecting each stop.
- Watch for an entry missing between two seamed neighbors, or a blank band. The omission is probabilistic (roughly 1 in 4-6 up/down passes); repeat the cycle a few times.
Reproduces with a real mouse wheel and with programmatically injected SGR wheel reports (\x1b[<64;x;yM / \x1b[<65;x;yM), during active streaming turns and on idle completed history, at multiple grid sizes (tested 210x48 and 306x15).
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
Unknown - reproduced and frozen on both 2.1.220 and 2.1.221, so not introduced by 2.1.221.
Claude Code Version
2.1.220 (Claude Code) and 2.1.221 (Claude Code) - both reproduced
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Other (embedded xterm.js host under node-pty/ConPTY; Windows 11 build 26200)
Additional Information
Evidence that the omission is in the CLI's output, not the terminal displaying it: capturing the raw PTY output stream (independently of any display) and parsing it shows the missing entries' bytes are never emitted at that scroll position - the parsed capture matches the on-screen state exactly, including the omission, with both neighboring entries present. After ctrl+End + scroll-up, the same entries are emitted and render correctly, so the history model retains the content and the scrolled-window emission skipped it.
The scrolled history is visibly a live re-render (spinner verb labels re-roll between visits to the same region, e.g. "Cooked for 32s" -> "Crunched for 32s"; tool summary lines occasionally recompose between renders), which points at the window-assembly step skipping entries under scroll rather than data loss - consistent with the ctrl+End repair restoring everything.
CLAUDE_CODE_ALT_SCREEN_FULL_REPAINT=1 plausibly masks the bug by removing incremental updates from the path (validating separately). If confirmed, the defect is specific to incremental-update emission for scrolled views.
Screenshots of both flavors are available - will attach on request.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Validation update: ran extended testing with CLAUDE_CODE_ALT_SCREEN_FULL_REPAINT=1 on v2.1.221. The closed-up flavor stopped reproducing entirely, but the blank-band flavor still occurs: after a deep scroll-up and ride back down, a band of empty rows appears where several consecutive entries belong, flanked by correctly rendered neighbors, and a further scroll or ctrl+End restores them. Since every frame is a full repaint under this flag, the emitted frame itself contains the blank rows - so at least part of the omission happens when the scrolled window's content is assembled (entries not yet materialized render as empty rows), independent of the incremental-update path. This suggests two distinct layers: an incremental-update defect (masked by the flag) and a window-assembly defect (not maskable). One more data point: the same transcript region has now produced omissions across both flag states and both versions - the region contains several long wrapped lines, so entry height accounting may be involved.
Byte-level capture of an extreme flavor of this: the scrolled-window emission can omit the entire transcript, not just a band.
Trigger: in a long fullscreen session, jump with ctrl+End (ESC[1;5F, from the very top) or ctrl+Home (ESC[1;5H, from the bottom). Intermittently the next repaint erases all rows and draws only the chrome: banner, the Jump-to-bottom pill, separators, and the input/status lines. Captured on 2.1.222 by tapping the PTY stream in the host terminal (xterm.js, 210x48 grid): the two frames emitted after the jump were 1846 and 2117 bytes; each begins ESC[?2026h ESC[?2026l ESC[?25l ESC[H, then erases every row (per-row ESC[K and space runs), draws the chrome rows, and ends ESC[45;3H ESC[?25h. Zero history entries are present in the frames, and the host terminal's buffer confirms it (9 non-empty rows, all chrome). Healthy jumps moments later emitted 5.4-7.7KB frames carrying the full viewport.
Because the TUI is idle after the jump, nothing re-renders: the screen stays black until any interaction (a scroll tick, a keypress, a resize) forces the next frame, which then paints the correct viewport. That matches the observation above that the history model still holds the content; only the emission skipped it.
Happy to attach the full hex dumps if useful.
Same here on 2.1.260 (macOS, xterm.js-based terminal): scrolling sometimes produces blank rows and regions. Capturing the raw output confirms Claude Code never sends them until something like a click makes it repaint. Any long context scroll will have at least one instance.