Skip to content

fix(terminal): only forward scroll to Claude while it tracks the mouse - #498

Merged
Ark0N merged 1 commit into
Ark0N:masterfrom
JDProfresh:fix/claude-inline-scroll
Sep 28, 2026
Merged

Ark0N merged 1 commit into
Ark0N:masterfrom
JDProfresh:fix/claude-inline-scroll

Conversation

@JDProfresh

Copy link
Copy Markdown

Problem

On Claude Code 2.1.280, wheel and touch scrolling in a Claude session does nothing, while the same gesture scrolls a Codex session fine. Reproduced on iOS Safari; the desktop wheel takes the same path.

Cause

_shouldForwardWheelToApp() forwards every Claude session at or above 2.1.187 as SGR wheel reports, on the assumption that Claude pages its own transcript. That only holds in Claude's fullscreen renderer. Measured on 2.1.280 in a bare tmux:

Claude 2.1.280 alternate_on mouse_any_flag mouse_sgr_flag
default 0 0 0
CLAUDE_CODE_NO_FLICKER=1 1 1 1

In the default inline renderer Claude never enables mouse tracking and keeps its transcript in real scrollback (the affected live pane had history_size=1018), so the forwarded reports are ignored and the gesture is dead. This is the same failure #227 fixed for Codex.

Fix

Forward only while the server-observed cliMouseTracking flag is true (already recorded by _recordStrippedMouseMode and used by _shouldReportMouseToCli for taps). Inline Claude now scrolls local scrollback like Codex; fullscreen Claude keeps forwarding. A stale-false flag after a server restart falls through to the existing PageUp/PageDown fallback (_maybePageCliTranscript), so the gesture is never dead.

Docs describing the forwarding rule (CLAUDE.md, docs/architecture-invariants.md, two wiki pages) are updated to match.

Open question

The #144 and #205 work measured Claude panes with history_size=0 and verified forwarding against 2.1.202, which suggests Claude was running fullscreen there. I could not tell whether Claude's default renderer changed since then or those setups had fullscreen enabled. The fix does not depend on which, since it checks the live mouse-tracking state.

Testing

  • New regression case in test/terminal-touch-tap.test.ts (inline 2.1.280 does not forward; with cliMouseTracking it does); existing forwarding fixtures now set cliMouseTracking: true.
  • npm test: 8300 passed.
  • Deployed to a live instance and confirmed that touch scrolling works in a Claude session on iOS Safari.

Claude 2.1.280 renders inline by default: no alt screen, no mouse tracking, transcript in real scrollback. The version-only gate still sent every wheel tick and touch swipe as SGR reports, which Claude ignores, so scrolling a Claude session was dead while codex (routed locally) worked. Gate forwarding on the server-recorded cliMouseTracking flag, which fullscreen mode (CLAUDE_CODE_NO_FLICKER=1) sets.
@Ark0N
Ark0N merged commit c9f47b0 into Ark0N:master Sep 28, 2026
2 checks passed
Ark0N pushed a commit that referenced this pull request Sep 28, 2026
…#498)

- claude's declared-for-later wheelForward says the live rule in
  _shouldForwardWheelToApp is the version AND the server-published
  cliMouseTracking flag, so whoever wires the field up needs both

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Sep 28, 2026
- _logScrollRouting() reports cliMouseTracking, the gate's new input, in both
  the de-dup signature and the console line (xterm's own mouseTracking stays
  'none' for Claude, so it gave no reason for a no).
- Restore two guard tests the new gate made vacuous: the local-scrollback
  opt-out footgun test and the codex/gemini "no version rescues it" fixtures
  now set cliMouseTracking: true, so removing the opt-out or re-adding codex to
  the gate fails again.
- Update the comments and architecture-invariants lines that still described
  the version-only rule (wheel handler header, gate doc, the false paths of
  _maybePageCliTranscript, "holds a tracking mode on continuously").
- Name both fullscreen switches (CLAUDE_CODE_NO_FLICKER=1 and "tui":
  "fullscreen" in ~/.claude/settings.json) in the code comment, the invariants
  and the two wiki pages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Ark0N

Ark0N commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Merged, and it ships in 1.33.2. Thanks @JDProfresh.

The tmux measurement table is what made this easy: inline Claude never turns mouse tracking on, so gating on the cliMouseTracking flag the server already records is the right fix, and fullscreen sessions keep forwarding. Applied on the way in:

  • _logScrollRouting() now prints cliMouseTracking, so a "scroll does nothing" report shows why the gate said no.
  • Two existing guard tests had gone vacuous because their fixtures lacked cliMouseTracking: true (the local-scrollback opt-out test, and the codex/gemini "never forward" pins from fix(terminal): scrollback round 2 for #205 (re-pull downgrade guard, PageUp fallback, CLI version probe retry) #227). The fixtures set it now, and re-adding codex to the mode check fails the test again.
  • The comments in terminal-ui.js, docs/architecture-invariants.md and both wiki pages now describe the version-plus-tracking rule and name both fullscreen switches: CLAUDE_CODE_NO_FLICKER=1 and "tui": "fullscreen" in ~/.claude/settings.json.

@JDProfresh
JDProfresh deleted the fix/claude-inline-scroll branch September 28, 2026 19:23
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.

2 participants