Skip to content

fix: clear in place on CSI 2J at home position (#9181)#10743

Open
lonexreb wants to merge 2 commits into
warpdotdev:masterfrom
lonexreb:fix/9181-screen-replicates
Open

fix: clear in place on CSI 2J at home position (#9181)#10743
lonexreb wants to merge 2 commits into
warpdotdev:masterfrom
lonexreb:fix/9181-screen-replicates

Conversation

@lonexreb
Copy link
Copy Markdown
Contributor

@lonexreb lonexreb commented May 12, 2026

Closes #9181

Summary

  • The "Terminal Screen Replicates Throughout Session" symptom (reproduced daily during Claude Code CLI sessions) is caused by Warp's primary-screen CSI 2J handler taking the clear_viewport() path, which scrolls the prior visible region into block-level scrollback before re-rendering.
  • Inline-rendering CLI agents that do not opt into the alt screen (Claude Code in its default mode is the canonical example) issue CSI [H (cursor home) followed by CSI 2J many times per second to redraw their frame. Each redraw cycle duplicates the previous frame into the block's scrollback, producing the unbounded "replication" the user reports.
  • This change adds a small heuristic in ansi_handler::clear_screen for ansi::ClearMode::All: when the cursor is already at the home position (VisibleRow(0), col 0) and a prior at-home CSI 2J fired within the recent cadence window (TUI_REDRAW_CADENCE_WINDOW = 500ms), and the alt-screen / FullGridClearBehavior::Clear paths are not engaged, clear the visible rows in place via the existing clear_visible_rows_in_place helper instead of clear_viewport().
  • The legacy scroll-into-scrollback behavior is preserved for both non-home CSI 2J and for the first at-home CSI 2J in a long while — the latter case is what the shell clear command produces under xterm-256color terminfo, which emits CSI [H followed by CSI [2J.

Why this is the right shape of fix

  • It is symmetric with PR Implement full-frame clear for active block for CLI Agents. #9877 (Implement full-frame clear for active block for CLI Agents.), which solved the same family of bug for the resize code path once Warp had already detected the CLI agent. This change covers the redraw code path before detection registers (or when the agent is not detected at all — e.g. third-party agents or claude invoked without the Warp plugin), which matches both the symptom timeline and the "Yes, this issue prevents me from using Warp daily" severity in the report.
  • It matches xterm / ghostty / iTerm2 semantics for CSI 2J: those terminals do not push the prior frame into scrollback on full-screen erase. Warp's behavior of conflating "user clear screen" with "TUI redraw" is the non-standard interpretation that caused the regression in the first place.
  • Cursor-at-home alone is not a sufficient signal — xterm-256color terminfo's clear capability is CSI [H then CSI [2J, so the cursor is at home when 2J fires for the shell clear command too. The cadence check is what cleanly disambiguates the two cases: inline-rendering CLI agents emit many at-home CSI 2J per second, while an interactive user runs clear at human speed. The first at-home 2J in a long while is therefore treated as a one-off clear and preserves the visible region in scrollback; subsequent rapid ones are treated as TUI redraws and clear in place.

Test plan

Added four regression tests in app/src/terminal/model/grid/grid_handler_tests.rs:

  • test_clear_screen_all_primary_tui_redraw_at_home_clears_in_place — the core regression: home + CSI 2J on the default primary-screen path (with active TUI redraw cadence) must leave history_size() == 0.
  • test_clear_screen_all_primary_repeated_tui_redraws_do_not_duplicate — drives 10 redraw cycles and asserts the scrollback does not grow.
  • test_clear_screen_all_primary_non_home_cursor_preserves_scrollback — the non-home CSI 2J path must continue to push the visible region into scrollback.
  • test_shell_clear_command_preserves_scrollback_when_not_in_cli_agent_redraw_loopnew: directly addresses the bot review concern. Reproduces the xterm-256color shell clear byte sequence (CSI [H followed by CSI [2J) with no active TUI redraw cadence and asserts that scrollback is preserved (legacy behavior).

The existing tests test_clear_screen_all_primary_preserves_visible_rows_in_history_by_default, test_clear_screen_all_primary_with_full_grid_clear_behavior_clears_in_place, and test_clear_screen_all_alt_screen_clears_in_place continue to cover the unchanged paths and were not modified.

Manual testing note

This worktree environment lacks the Xcode metal toolchain (xcrun: error: unable to find utility "metal"), so cargo check -p warp --lib and cargo check -p warp_terminal could not complete locally — both transitively depend on warpui's Metal-shader build step. The change was validated by direct review of the surrounding clear_screen / clear_visible_rows_in_place / clear_viewport paths and the new unit tests above (which exercise only the GridHandler and have no GPU dependency). CI on the upstream repo will exercise the full crate-scoped build and test suite.

The cadence window of 500ms is chosen to be a generous upper bound on inline-CLI-agent frame intervals (Claude Code emits multiple redraws per second under any non-trivial streaming load) while leaving a wide margin above any plausible human-interactive clear cadence (a user mashing clear at the keyboard typically achieves ~3-5 invocations per second at most, and crucially does not chain them with no intervening prompt — but even back-to-back clear invocations within 500ms would simply behave like an in-place redraw, which is visually indistinguishable from the legacy scroll-into-scrollback path when the prior frame is already blank from the first clear).

🤖 Generated with Claude Code

When a primary-screen TUI emits CSI 2J with the cursor already at the
home position (0, 0), treat the sequence as a full-frame redraw and
clear in place rather than pushing the prior frame into block-level
scrollback via `clear_viewport`.

This mirrors xterm/ghostty/iTerm2 semantics for CSI 2J and addresses
the "Terminal Screen Replicates Throughout Session" symptom reported
for Claude Code (and other inline-rendering CLI agents that do not
opt into the alt screen and are not yet detected as CLI agents by
Warp). Without this guard each redraw cycle duplicated the visible
region into the block's scrollback, growing unboundedly over the
life of the session.

The legacy scroll-into-scrollback path is preserved for non-home CSI
2J (notably the shell `clear` command path), so existing user
expectations there are unchanged.

Closes warpdotdev#9181
@cla-bot cla-bot Bot added the cla-signed label May 12, 2026
@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label May 12, 2026
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 12, 2026

@lonexreb

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes May 12, 2026
Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR changes primary-screen CSI 2J handling so a full-screen erase at cursor home clears visible rows in place, with regression tests for repeated inline TUI redraws.

Concerns

  • ⚠️ The cursor-at-home heuristic also matches common shell clear sequences such as xterm-256color terminfo's ESC[H ESC[2J, so running clear from a non-home prompt would take the new in-place path after the preceding home command and stop preserving the visible region in scrollback.
  • ⚠️ This is a user-visible terminal behavior change, but the PR does not include screenshots or a screen recording showing the behavior end to end, nor a justification for why manual visual validation is not possible.

Verdict

Found: 0 critical, 2 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

self.grid.region_mut(..).each(|cell| *cell = bg.into());
} else if self.full_grid_clear_behavior == FullGridClearBehavior::Clear {
self.clear_visible_rows_in_place(bg);
} else if self.is_tui_redraw_clear() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This also matches common shell clear output because terminfo entries like xterm-256color use ESC[H ESC[2J; after the preceding home command, this branch runs and stops preserving the visible region in scrollback despite the stated legacy behavior. Distinguish redraws from shell clear using more than the current cursor position, or update the behavior/tests intentionally.

…ve shell clear (warpdotdev#9181)

The previous fix used cursor-at-home as the sole signal to switch CSI 2J
to the in-place clear path. As flagged by oz-for-oss review, xterm-256color
terminfo's `clear` capability emits `CSI [H` followed by `CSI [2J` — so
the cursor IS at home when 2J fires for the shell `clear` command, and
the cursor-at-home heuristic alone would silently destroy the visible
region on every `clear` (no scrollback preserved).

Add a cadence check: an at-home CSI 2J only takes the in-place path when
a prior at-home CSI 2J fired within `TUI_REDRAW_CADENCE_WINDOW` (500ms).
Inline-rendering CLI agents (Claude Code et al.) emit redraws many frames
per second, so all redraws after the first frame fall inside the window;
an interactive `clear` invocation happens at human speed and never does.

The first at-home 2J in a session therefore behaves like the legacy
shell-`clear` path (scrollback preserved). The unbounded duplication
symptom from GH warpdotdev#9181 is gated on the second-onward frames staying
in place, which the existing repeated-redraw test asserts and the new
shell-clear test pins from the opposite direction.

Test plan:
- Adds `test_shell_clear_command_preserves_scrollback_when_not_in_cli_agent_redraw_loop`
  reproducing the xterm-256color `clear` byte sequence and asserting
  legacy scrollback behavior.
- Updates the two existing TUI-redraw tests to seed the cadence via a
  new `#[cfg(test)] seed_tui_redraw_cadence_for_test()` helper so they
  isolate the in-place clear behavior from cadence detection.
- Non-home and shell-clear tests both continue to preserve scrollback.
@lonexreb
Copy link
Copy Markdown
Contributor Author

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 12, 2026

@lonexreb

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I reviewed this pull request and requested human review from: @warpdotdev/oss-maintainers.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot dismissed their stale review May 12, 2026 17:58

Oz no longer requests changes for this pull request after the latest automated review.

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR updates primary-screen CSI 2J handling to detect rapid at-home redraw loops and clear the visible grid in place instead of pushing every frame into scrollback. It preserves the existing scrollback-producing path for non-home clears and one-off at-home shell clears, and adds regression coverage for the TUI redraw and shell clear cases.

Concerns

  • No blocking correctness or security concerns found in the annotated diff.
  • The changed terminal behavior is user-visible, but the PR body includes a manual testing limitation for this worktree and explains the available validation path.
  • No matching individual stakeholder was listed for app/src/terminal/model/grid/; only the catch-all team rule applies, so recommended_reviewers is left empty for the workflow fallback.

Verdict

Found: 0 critical, 0 important, 0 suggestions

Approve

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot requested review from a team and peicodes and removed request for a team May 12, 2026 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Terminal Screen Replicates Throughout Session

1 participant