Skip to content

Add scroll-fix plugin: fixes terminal scroll-to-top regression (all platforms)#35683

Open
cruzlauroiii wants to merge 1 commit intoanthropics:mainfrom
cruzlauroiii:plugin/scroll-fix
Open

Add scroll-fix plugin: fixes terminal scroll-to-top regression (all platforms)#35683
cruzlauroiii wants to merge 1 commit intoanthropics:mainfrom
cruzlauroiii:plugin/scroll-fix

Conversation

@cruzlauroiii
Copy link
Copy Markdown

Summary

Adds a plugin that fixes the terminal scroll-to-top regression affecting all platforms and terminals.

  • Automatic fix: Intercepts and clamps cumulative cursor-up within synchronized output blocks to , keeping the cursor within the visible viewport
  • Ctrl+6 freeze toggle: Manual output freeze/unfreeze for scrolling through history without viewport being yanked back
  • Install script: to patch cli.js directly
  • Preload module: for runtime-only fix

Root cause

Ink renderer's emits N cursor-up () sequences where N = previously rendered line count. When N > viewport height, terminals follow the cursor above the visible area, snapping to the top of scrollback. This happens inside synchronized output blocks so even DEC mode 2026 doesn't prevent it — the terminal follows the final cursor position.

Affects all terminals (Windows Terminal, iTerm2, Terminal.app, VS Code, tmux, Kitty, etc.) on all platforms.

Plugin structure

plugins/scroll-fix/
├── .claude-plugin/plugin.json
├── scroll-fix.cjs # Preload module (--require)
├── hooks/hooks.json # SessionStart hook
├── scripts/
│ ├── check-scroll-fix.js # Hook handler
│ └── install.js # CLI patcher (install/uninstall)
└── README.md

Duplicates fixed

Upstream: microsoft/terminal#14774

Test plan

  • Verified cursor-up clamping prevents viewport scroll-to-top
  • Verified Ctrl+6 freeze/unfreeze works
  • Tested on Windows Terminal, VS Code terminal
  • Test on macOS (iTerm2, Terminal.app)
  • Test on Linux terminals

@cruzlauroiii
Copy link
Copy Markdown
Author

Updated with v2 scroll fix:

  • Root cause found: readline/prompt's eraseLines(this.height) sends cursor-up OUTSIDE sync blocks (missed by v1)
  • v2 fix: clamps ALL cursor-up per write call to viewport height (no sync block tracking needed)
  • Ctrl+6 freeze toggle works
  • git diff compatible: patches/scroll-fix.patch applied via git apply -C0, fallback to string injection
  • Marketplace install: /plugin install scroll-fix@cruzlauroiii-plugins
  • Added to marketplace.json

…latforms)

- Clamps ALL cursor-up per write call to viewport height
- Catches both Ink renderer AND readline/prompt eraseLines
- Ctrl+6 freeze toggle for manual scroll control
- git diff compatible patches (git apply -C0)
- Marketplace install: /plugin install scroll-fix@cruzlauroiii-plugins

Fixes anthropics#33814, anthropics#826, anthropics#11801, anthropics#3648, anthropics#34794, anthropics#33367, anthropics#34242
@jancbeck
Copy link
Copy Markdown

@Anthropic please fix this upstream, I don't want to install a minified JS patch just so I can have long sessions in Claude Code

@nullbio
Copy link
Copy Markdown

nullbio commented Mar 19, 2026

@ashwin-ant

@nikicat
Copy link
Copy Markdown

nikicat commented Mar 19, 2026

Bug report: plugin hangs all child Node.js processes

The scroll-fix.cjs plugin, when loaded via NODE_OPTIONS="--require .../scroll-fix.cjs", causes every child Node.js process to hang indefinitely.

Root cause

The setTimeout on line 47 fires after 2 seconds and attaches a process.stdin.on("data", ...) listener. This listener keeps the Node.js event loop alive. Since NODE_OPTIONS is inherited by all child processes, tools like node-gyp-build, del-cli, and any lifecycle script spawned by npm/pnpm will load the plugin, attach the stdin listener, and never exit.

Impact

pnpm install (and likely npm install) hangs during lifecycle script execution. The parent package manager waits for child processes that will never terminate.

Suggested fix

unref() both the timeout and stdin so they don't prevent natural process exit:

var _t = setTimeout(function () {
    try {
        process.stdin.on("data", function (d) { ... });
        process.stdin.unref();
    } catch (e) {}
}, 2000);
_t.unref();

Or guard against non-interactive contexts entirely (e.g. skip when !process.stdout.isTTY).

@jwhitcraft
Copy link
Copy Markdown

Even with the patch installed, i'm still getting some random flashes, iterm2 with tmux. It's fine in my VSCode Terminal without tmux.

The flashes seems less sporadic as they are with out the patch.

@cruzlauroiii
Copy link
Copy Markdown
Author

cruzlauroiii commented Mar 19, 2026 via email

@nullbio
Copy link
Copy Markdown

nullbio commented Mar 21, 2026

Still broken as of v2.1.81:

#36582
#35403
#36816
#35683
#33814
#34845
#33367
#34400
#826
#36621
#36128
#35766
#34242
#18299

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants