fix(terminal): cap scrollback and cache/chunk restore (fixes #581) - #596
Open
TyroneNel wants to merge 5 commits into
Open
fix(terminal): cap scrollback and cache/chunk restore (fixes #581)#596TyroneNel wants to merge 5 commits into
TyroneNel wants to merge 5 commits into
Conversation
…eeze (cline#581) The terminal restore path ships a full serialize() snapshot of the headless xterm buffer on every task-switch WebSocket connect, and the browser re-parses/re-renders the entire buffer in an xterm configured with the same 10,000-line scrollback. With a long agent run this blocks the main thread for ~60s ("page unresponsive") and drives RSS toward the cline#273 OOM. Reduce TERMINAL_SCROLLBACK on both sides from 10,000 to 1,000 lines. This caps the worst-case snapshot size at ~10x smaller, eliminating the main-thread block on task switch. The full-snapshot replay is still O(scrollback) — a P0 follow-up should send viewport-only deltas and lazy-load older scrollback on scroll — but this is the minimal, low-risk change that stops the freeze today. Refs cline#581, cline#273
Serialize the full 1k headless buffer on demand, then reuse that
snapshot until applyOutput or resize marks it dirty. Connect no
longer pays serialize on every viewer if the PTY has not changed.
The snapshot still includes lines above the viewport. Do not use
serialize({ scrollback: 0 }).
Split the full 1k snapshot into ~16 KiB chunks and enqueue each write so xterm parses restore on a budget instead of one huge write. restore_complete still waits until the write queue drains.
Start of a PTY bumps restoreGeneration. Control reconnects send that id with the cached snapshot. A viewer that already applied the same generation at the same cols x rows sends restore_complete without reset or rewrite. Old servers that omit the field still apply.
The client buffer is room for the last 1k restore lines. Restore payload size comes from the cached server serialize, not from keeping two constants in lockstep.
|
PR author is not in the allowed authors list. |
This was referenced Aug 18, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
When a user switches tasks, the server sends a restore snapshot of the headless terminal. The browser writes that snapshot in one step. A 10,000-line buffer blocked the UI for about 60 seconds. The same path also grew RSS toward the OOM in #273.
#583 caps the buffer at 1,000 lines. A later commit on that PR uses
serialize({ scrollback: 0 }). That call keeps only the current screen. It removes lines above the viewport.Decision
Keep the 1,000-line cap from #583.
Do not ship viewport-only
serialize({ scrollback: 0 }). Restore still sends the last 1,000 lines.Add three controls:
restoreGeneration. If this viewer already applied that generation at the same cols and rows, it does not reset and it does not write again. It still sendsrestore_complete. If the server omitsrestoreGeneration, the viewer always applies the snapshot.Parked terminals keep their sockets. A card switch on an already-open terminal does not restore. Restore runs on first open, on refresh, and after the socket dies.
Domain words
restore_complete.Consequences
The last 1,000 lines stay scrollable. The UI does not block on a 10,000-line write. A warm client that already has the current PTY does not reset.
This PR does not add two-phase restore, lazy-load on scroll, a worker-thread serialize, or a raw PTY ring.
Test plan
Automated:
test/runtime/terminal/terminal-state-mirror.test.ts— snapshot includes lines above the viewport. A second call with no output reuses the cache. Output or resize rebuilds the snapshot.web-uirestore-snapshot-chunks— empty input returns no parts. One byte over 16 KiB returns two parts. Join of parts equals the input.web-uirestore-generation— skip only when generation, cols, and rows match.ws-serverand session-manager — restore includes generation. The same session keeps that generation. A new PTY increases it.npm run check— biome, typecheck, and tests. Two integration files timed out when they ran in parallel with the web-ui suite. They passed when they ran alone.npm --prefix web-uitypecheck and testsnpm run buildManual (after merge or local run):
Fixes #581
Related: #583, #273