[BUG] Terminal scrolls to top during agent execution (Windows Terminal + PowerShell)
Description
While Claude Code is executing tools/commands, the terminal viewport jumps to the topmost position of the PowerShell/terminal buffer. This happens repeatedly during agent work, forcing the user to scroll back down constantly. Expected behavior: maintain the current scroll position so the user can continue working.
Root Cause Analysis (from cli.js v2.1.76 source)
Claude Code uses Ink (React-based terminal UI) for rendering. Ink's rendering strategy:
- On each re-render, it moves the cursor UP to the start of the component area
- Erases all previous lines (
eraseLines(height)) - Writes the new content
The core rendering code (from the bundled @inquirer/core and Ink output):
// Moves cursor up, erases, and rewrites
this.write(
cursorDown(this.extraLinesUnderPrompt) +
eraseLines(this.height) +
newContent
)
Where eraseLines is:
eraseLines = (count) => {
let str = "";
for (let i = 0; i < count; i++)
str += eraseLine + (i < count - 1 ? cursorUp() : "");
if (count) str += cursorLeft;
return str;
}
The problem: When the output grows large (streaming responses, tool results), eraseLines(this.height) moves the cursor far up in the terminal buffer. On Windows Terminal, this cursor movement causes the viewport to follow the cursor to the top of the render area, even if the user has scrolled away.
This is a fundamental issue with Ink's diff-based terminal rendering on Windows Terminal. macOS Terminal.app and iTerm2 are also affected (per #34400, #34765).
Steps to Reproduce
- Start Claude Code in Windows Terminal (PowerShell)
- Give it a multi-step task (e.g., searching files, running commands)
- While it's working, scroll up to read previous output
- Observe: viewport snaps to top of terminal buffer on each re-render
Suggested Fix
Option A: Use alternate screen buffer for the active render area
Ink supports alternate screen buffer mode which prevents scroll interference:
// Enter alternate screen before rendering
process.stdout.write('\x1b[?1049h');
// Exit alternate screen when done
process.stdout.write('\x1b[?1049l');
Option B: Minimize re-render height
Instead of erasing the entire component and redrawing, only erase/redraw the lines that actually changed (incremental rendering).
Option C: Disable cursor movement when user has scrolled
Detect if the user has scrolled away from the bottom and suppress cursor-up movements until they scroll back.
Environment
- OS: Windows 11 Pro 10.0.26200
- Terminal: Windows Terminal
- Shell: PowerShell / bash (Git Bash)
- Claude Code: 2.1.76
- Source: cli.js SHA-256
38b8fd29d0817e5f75202b2bb211fe959d4b6a4f2224b8118dabf876e503b50b
Related Issues
- #34400 — Scroll position resets to top during agent execution (macOS/iTerm)
- #34765 — Scroll position resets to top of session while processing
- #33814 — Forces scroll to top when outputting code
- #34052 — Scroll to top always on CLI
- #34503 — Terminal scrolls to top on every new output
- #33624 — Forced scroll when window gains focus (Windows Terminal + WSL2)
16 Comments
Updated with v2 fix in PR #34798. The v1 approach (patching LH8/exitAlternateScreen/handleResume) only fixed edge cases. v2 injects a global stdout.write interceptor that clamps cursor-up across ALL writes, catching the primary sources: Ink's hV7() clear function and the Inquirer prompt renderer.
Updated PR #34798 with 3 combined patches: (1) stateful stdout.write interceptor clamping cursor-up, (2) render throttle 16ms→200ms, (3) disable synchronized update mode. All three are needed — cursor-up clamping alone does not fix it because Windows Terminal exits scroll mode on ANY stdout write, and sync blocks batch writes into one viewport-resetting update. PowerShell apply/revert script included.
v3 update: Previous fixes (cursor-up clamping, disabling sync update) did not work and caused flickering.
Root cause identified: Windows Terminal bug microsoft/terminal#14774 —
SetConsoleCursorPositionalways scrolls viewport to cursor, even when visible. Every Ink re-render triggers this. Cannot be fixed from within the process.v3 patch: render throttle
SK6=16→SK6=1000(1fps). Gives 1 second of uninterrupted reading between viewport resets. Trade-off: streaming text in ~1s chunks. No flickering.PR #34798 updated. Real fix needs Microsoft (terminal#14774) or Anthropic (PTY proxy/append-only rendering).
v5 update: Fundamentally different approach — buffer ALL Ink renders during active work, screen stays frozen (no viewport jumping). Flush only when (a) user types (at prompt/bottom, safe to update) or (b) 5s of no renders (Claude finished). Previous v1-v4 failed because WT bug microsoft/terminal#14774 triggers viewport scroll on ANY cursor positioning. v5 avoids this by not writing to stdout at all during active work. Trade-off: no spinner/progress visible during work. PR #34798 updated.
v6: Buffer ALL renders, flush ONLY on user input (stdin). No timer. No auto-flush. Zero viewport jumping while not at bottom. Root cause: WT bug microsoft/terminal#14774. PR #34798.
v7: Replay ALL buffered frames on flush (fixes v6 outdated screens). Keeps entire diff chain so screen content is always correct. Still zero viewport jumping — flush only on user input (stdin). PR #34798.
Final fix: Ctrl+6 freeze toggle. Press Ctrl+6 to freeze screen (buffer renders, scroll freely). Press again to unfreeze (replay + live output). Tab title shows [FROZEN]. Root cause is WT bug microsoft/terminal#14774 — cannot be detected/fixed automatically. PR #34798.
A fix for this is available as a Claude Code plugin: https://github.com/anthropics/claude-code/pull/35683
The scroll-fix plugin clamps cursor-up sequences within synchronized output blocks to the viewport height, preventing the terminal from scrolling to the top. Also includes Ctrl+6 freeze toggle for manual scroll control. Works on all platforms and terminals.
A fix is available as a Claude Code plugin: scroll-fix
Install:
Root cause: both Ink renderer AND readline/prompt system emit cursor-up sequences exceeding viewport height. The plugin clamps all cursor-up per write call. Also includes Ctrl+6 freeze toggle.
PR: https://github.com/anthropics/claude-code/pull/35683
This is like the 10th time this has regressed.
+1 — This is a significant productivity blocker.
Environment:
Impact: During any multi-step task, scrolling up to read prior output while Claude works is impossible — the viewport snaps away on every Ink re-render cycle. This forces a "wait until it's done" workflow that kills the ability to review work in progress, catch issues early, or stay oriented during long operations.
What I've tried:
"snapOnOutput": falsein Windows Terminal profile defaults — no effect on the primary issue (cursor-repositioning jumps)eraseLines(height)cursor-up sequences from Ink's rendererWhat works: tmux (full scroll isolation), but that requires running Claude Code inside WSL, which changes the entire workflow and breaks native Windows hooks/paths.
What would actually fix this: Anthropic replacing Ink's full-erase re-render strategy with incremental rendering (only redraw changed lines) or using the alternate screen buffer for the active render area. The root cause is in Claude Code's renderer, not in Windows Terminal — though WT bug microsoft/terminal#14774 compounds it.
This is the #1 UX issue preventing efficient use of Claude Code on Windows Terminal.
Also seeing this on iTerm2 (macOS, Darwin 25.3.0). The viewport jumps to the top of the scroll buffer during tool execution and TUI redraws. Tried toggling several iTerm2 settings (scroll to bottom on output, save lines to scrollback with status bar present, alternate mouse scroll) — none resolved it.
Been following this thread and built something to address it — sharing in case it helps people while waiting for an upstream fix. Genuinely curious whether it helps people's specific setups.
Quell is a Windows terminal built specifically for Claude Code. It intercepts VT output via ConPTY and strips the sequences responsible for the scroll-jumping before they ever reach the renderer.
What it fixes (as of v0.2.1):
All three root causes now handled:
ESC[3J— erase scrollback — stripped unconditionallyESC[2J— erase display — stripped outside DEC 2026 synchronized update blocks (allowed inside, where it's part of an atomic redraw)ESC[nA— cursor-up — stripped outside sync blocks (v0.2.1, shipped two days ago)The sync-block awareness matters for the latter two: Ink wraps its redraws in BSU/ESU markers, so we can distinguish "this is a legitimate UI repaint" from "this is orphaned cursor movement that will snap the viewport." The sed patch approach strips cursor-up unconditionally — which works, but also strips legitimate cursor movement. Our approach is more surgical.
Two ways to use it:
GUI (recommended — eliminates the problem entirely): A standalone terminal app — download the installer. Because we own the renderer (xterm.js), the cursor-up issue has nowhere to propagate to. This is the cleanest fix.
CLI proxy: For people who want to stay in their current terminal —
quell claudewraps Claude Code in the filter layer. Handles ESC3J and ESC[2J completely. Cursor-up handling helps significantly, though in very long sessions you may still see minor drift from [microsoft/terminal#14774 — that's an upstream terminal behaviour we can mitigate but not fully control from a proxy position.What I'd genuinely like to know:
Does this actually solve it for people's real sessions? The cases I'm most uncertain about are very long sessions (1000+ line scrollback) and heavy tool-use sequences where Ink redraws rapidly. If anyone tries it and finds cases where the jumping still occurs, I'd like to understand exactly what's happening — logs, repro steps, whatever's useful.
Also — now that Claude Code is 'open source', I'm looking at whether proposing that Ink adopt sync markers more consistently is viable. That would make the proxy approach essentially lossless for both CLI and GUI users. If anyone's interested in that angle, happy to discuss.
Windows only for now (ConPTY is the core of the fix). MIT licensed, no telemetry.
hey. it's fixed at the terminal level here
https://github.com/Dcouple-Inc/Pane/pull/120
open source, using xtermjs which is the same terminal as VS Code. feel free to copy this fix or download the latest release to run claude code on any operating system with the fix in place. i was so mad and frustrated at this that I had to fix it myself.
yeah i had to switch off cursor/vscode and build myself an ide that managed xtermjs (the library they both use) well enough for the complex TUIs to not break.
works great and i have a fix for the scroll jump i just implemented rn here
https://github.com/Dcouple-Inc/Pane/pull/120
This is a duplicate of #35403, which was fixed as of version 2.1.101.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.