[BUG] DECSTBM scroll regions stay gated after the terminal confirms synchronized output (gate evaluated before the DECRQM 2026 probe settles)

Status Open
Reported on v2.1.285
Maintainer reply None cached
Activity 0 comments · opened Sep 30, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

In a terminal that Claude Code doesn't recognise by name, the synchronized-output probe comes back positive, but DECSTBM scroll regions stay off for the whole session. With the fullscreen renderer, every scroll step is then sent as positioned writes over most of the screen instead of a region scroll. The frames are much larger, and scrolling is visibly worse than it needs to be.

I hit this in Orca (Electron, xterm.js 6.1.0-beta.303, TERM_PROGRAM=Orca). It should affect any xterm.js-based terminal that isn't identified by TERM_PROGRAM.

Reading the bundle, the cause looks like an ordering problem. The bundle is minified, so the names below are the minifier's (from 2.1.285; the same code is in 2.1.284 under other names) and this is an inference:

function g({ beforeTheAnswer: r }) {
  ...
  if (!a.TMUX && a.CLAUDE_CODE_FORCE_SYNC_OUTPUT) return true;
  let n = TO().get("synchronizedOutput");
  if (n.state === "settled") return n.value;
  return r && n.likely;
}
function $pe() { return g({ beforeTheAnswer: true }); }
function QAn(r = b8()) {
  return r && a.TMUX == null && process.env.ZELLIJ == null
    && !rT.isJetBrainsIdeTerminal() && !am() && a.WT_SESSION == null;
}
var ZAn = QAn($pe());   // module-level constant

ZAn is the flag the renderer checks (ZAn && !this.altScreenFullRepaint && !this.skipSyncMarkers() && b8()), and it is computed once, at module load. At that point the capability probe hasn't answered yet, so g({beforeTheAnswer: true}) falls back to the likely prior. For a terminal outside the recognised set that prior is false, so ZAn stays false even after the probe settles to yes a few milliseconds later. The per-frame b8() check does read the settled answer, but it is ANDed with a constant that is already false.

CLAUDE_CODE_FORCE_SYNC_OUTPUT=1 works around it because it returns early from g(), before the prior is consulted.

What Should Happen?

When the terminal answers DECRQM 2026 positively and none of the exclusions apply (tmux, Zellij, JetBrains, Windows Terminal), DECSTBM should be enabled, the same as when CLAUDE_CODE_FORCE_SYNC_OUTPUT=1 is set.

Concretely: re-evaluate the gate once synchronizedOutput settles, or read it lazily at first render instead of at module load.

This is a request to trust the probe, not to add Orca to an allowlist. An allowlist was declined for OSC 8 in #69625, and it wouldn't help other xterm.js-based terminals.

Error Messages/Logs

# default launch
XTVERSION: terminal identified as "xterm.js(6.1.0-beta.303)"
Terminal capabilities: extendedKeys=yes (probe: CSI ? u flags=0); synchronizedOutput=yes (probe: DECRPM 2026 status=2); kittyKeyboard=yes (probe: CSI ? u flags=0); kittyGraphics=no (probe: graphics reply OK, terminal xterm.js(6.1.0-beta.303)); mousePixels=yes (probe: DECRPM 1016 status=2, cell 8x16px)
DECSTBM: gated (TMUX=unset ZELLIJ=unset TERM_PROGRAM=Orca TERM=xterm-256color)

# same terminal, CLAUDE_CODE_FORCE_SYNC_OUTPUT=1
Terminal capabilities: ... synchronizedOutput=yes (env: CLAUDE_CODE_FORCE_SYNC_OUTPUT) ...
DECSTBM: enabled (TMUX=unset ZELLIJ=unset TERM_PROGRAM=Orca TERM=xterm-256color)

Steps to Reproduce

  1. Open a terminal pane in Orca (any xterm.js-based terminal whose TERM_PROGRAM Claude Code doesn't recognise should behave the same way).
  2. Run claude --debug-file /tmp/cc.log with "tui": "fullscreen".
  3. Run grep -E 'XTVERSION:|Terminal capabilities|DECSTBM:' /tmp/cc.log.
  4. You get synchronizedOutput=yes (probe: DECRPM 2026 status=2) followed by DECSTBM: gated.
  5. Repeat with CLAUDE_CODE_FORCE_SYNC_OUTPUT=1 claude --debug-file /tmp/cc2.log. You get DECSTBM: enabled, and scrolling in the fullscreen renderer is clearly smoother.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.285 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Other

Additional Information

  • Terminal: Orca 1.4.216 (xterm.js 6.1.0-beta.303), macOS 27.0, Apple Silicon.
  • The log lines above were captured on 2.1.284. I checked the 2.1.285 bundle before filing: the gate is still a module-level constant with the same logic, and the debug line still prints it.
  • I know the DECSTBM path had corruption issues on wide panes (#66013). If it's gated on purpose for terminals that aren't recognised, it would help to say so in the code and in the debug line: at the moment the log shows a positive probe followed by gated, with no reason given.
  • Related on the Orca side: stablyai/orca#16137 (fullscreen TUI wheel stutter; the remaining per-event cost is on Orca's side) and stablyai/orca#21960, where this gating was first spotted from the bundle.

View original on GitHub ↗