[BUG] Fullscreen: scroll-back sticky "last prompt" banner never appears since 2.1.247 (scrollRef -> scrollViewport.handle refactor)

Status Fixed / completed
Reported on v2.1.247
Maintainer reply None cached
Activity 2 comments · opened Sep 1, 2026 · closed Sep 22, 2026

What's Wrong?

In the fullscreen renderer, the sticky "last prompt" banner that appears at the top of the viewport while scrolled back — the muted one-line bar showing your most recent prompt, clickable to jump back to it — never appears at all from 2.1.247 onward.

Scrolling itself works perfectly (wheel, trackpad, Fn+↑/PgUp). The transcript moves as expected. Only the banner is missing, which makes this look like a theme or terminal problem rather than a renderer one.

What Should Happen?

Once the viewport is scrolled off the bottom and a user prompt has moved above the top of the visible area, a one-line bar should appear at the top showing that prompt's text, and clicking it should scroll back to it. This is the behaviour in 2.1.246 and earlier.

Steps to Reproduce

  1. "tui": "fullscreen" in ~/.claude/settings.json (or CLAUDE_CODE_NO_FLICKER=1)
  2. Send a prompt that produces a response longer than one screen
  3. Scroll up until your prompt passes above the top of the viewport

Expected: sticky banner with your prompt text appears at the top.
Actual (>= 2.1.247): no banner, ever. Scrolling works normally.

Is this a regression?

Yes.

Last Working Version

2.1.246

Claude Code Version

2.1.252 (also broken in 2.1.247 and 2.1.251)

Additional Information

Same-host bisect. Same machine, same tmux server, same terminal, same settings.json — only the binary differs:

| version | scroll plumbing in the sticky tracker | banner |
|---|---|---|
| 2.1.243 | pre-refactor | works |
| 2.1.246 | scrollRef + .current + .subscribe | works |
| 2.1.247 | scrollViewport + .handle (15 refs) | gone |
| 2.1.251 | same as .247 | gone |
| 2.1.252 | same as .247 | gone |

Likely mechanism. From the shipped bundles, the tracker subscribes to ot.subscribe but reads ot.handle, and arms via:

Rm = ot.handle?.isSticky() ?? !0;      // null handle -> true
if (Go > 0 && !Rm) { /* ...find prompt above viewport, arm banner... */ }

A null/absent handle defaults Rm to true, so the arming branch never runs. Scrolling continues to work because it goes through a different path, which is why the symptom presents as "everything works except the banner". In 2.1.246 the equivalent code read scrollRef.current instead.

Hypotheses ruled out, in case they save triage time:

  1. Renderer gating — the fullscreen predicate returns true; tmux display-message -p '#{alternate_on}' is 1 on both a working (2.1.246) and a broken (2.1.252) session.
  2. Prompt extractor — the function that pulls prompt text off a message is byte-identical across 2.1.246 / .247 / .251 / .252.
  3. Message shape — message.content being a string rather than a block array predates the break (present since 2.1.236, when the banner worked).
  4. Terminal / mouse routing — reproduces identically in Ghostty, Terminal.app and kitty; tmux is forwarding wheel events (send-keys -M, alternate_on=1, mouse_sgr=1, pane_in_mode=0), and it also reproduces with keyboard-only scrolling.
  5. Theme contrast — probed by overriding subtle to #ff00ff and userMessageBackground to #0000ff. The override visibly applies elsewhere in the UI and the banner still does not render, so it is genuinely absent rather than invisible.

Environment

  • macOS 26.6.2, arm64
  • Ghostty 1.3.1 + tmux 3.7c (TERM=tmux-256color); also reproduced in Terminal.app and kitty
  • "tui": "fullscreen"; no CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL, CLAUDE_CODE_DISABLE_MOUSE, or CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN set
  • not tmux -CC (#{client_control_mode} = 0), not a teammate session, not transcript mode

Workaround: run a pre-2.1.247 build (e.g. ~/.local/share/claude/versions/2.1.246) with DISABLE_AUTOUPDATER=1.

View original on GitHub ↗

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