Skip to content

Commit 415f02e

Browse files
Ark0Nclaude
andcommitted
fix: multi-layer backpressure to prevent terminal write freezes
Add three layers of protection against oversized terminal.write() calls that freeze Chrome's main thread: 1. SSE entry cap: _onSessionTerminal drops data when total queued bytes (pendingWrites + flickerFilterBuffer) exceeds 128KB. Server sends session:needsRefresh to recover dropped content. 2. Flush cap: flushPendingWrites splits at DEC 2026 sync segment boundaries with 64KB per-frame budget. Excess segments deferred to next requestAnimationFrame. Segment-level splitting preserves Ink redraw atomicity (no flicker). 3. Reduced tail size: initial buffer fetch reduced to 128KB (from 256KB) to limit data volume during tab switch. Re-enable WebGL renderer — root cause was unbounded terminal.write() volume, not the GPU renderer itself. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d581494 commit 415f02e

2 files changed

Lines changed: 13 additions & 3 deletions

File tree

src/web/public/app.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1386,7 +1386,7 @@ class CodemanApp {
13861386
// remaining segments to the next frame to prevent terminal.write() from blocking
13871387
// the main thread. 141KB single-frame writes have been observed to freeze Chrome
13881388
// for 2+ minutes even with the canvas renderer.
1389-
const MAX_FRAME_BYTES = 49152; // 48KB budget per frame
1389+
const MAX_FRAME_BYTES = 65536; // 64KB budget per frame
13901390
let bytesThisFrame = 0;
13911391
let deferred = false;
13921392

@@ -1815,6 +1815,16 @@ class CodemanApp {
18151815
_onSessionTerminal(data) {
18161816
if (data.id === this.activeSessionId) {
18171817
if (data.data.length > 32768) _crashDiag.log(`TERMINAL: ${(data.data.length/1024).toFixed(0)}KB`);
1818+
1819+
// Hard cap: track total bytes queued across ALL buffers (pendingWrites +
1820+
// flickerFilterBuffer + loadBufferQueue). When rAF is throttled (tab
1821+
// backgrounded, GPU busy), data accumulates with no flush, reaching
1822+
// 889KB+ and freezing Chrome for minutes. Drop data beyond 96KB —
1823+
// the server sends session:needsRefresh to recover.
1824+
const queued = (this.pendingWrites?.reduce((s, w) => s + w.length, 0) || 0)
1825+
+ (this.flickerFilterBuffer?.length || 0);
1826+
if (queued > 131072) return; // 128KB — drop to prevent accumulation
1827+
18181828
this.batchTerminalWrite(data.data);
18191829
}
18201830
}

src/web/public/constants.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ const TITLE_FLASH_INTERVAL_MS = 1500; // Title flash rate
5252
const BROWSER_NOTIF_RATE_LIMIT_MS = 3000; // Rate limit for browser notifications
5353
const AUTO_CLOSE_NOTIFICATION_MS = 8000; // Auto-close browser notifications
5454
const THROTTLE_DELAY_MS = 100; // General UI throttle delay
55-
const TERMINAL_CHUNK_SIZE = 32 * 1024; // 32KB chunks for terminal data (smaller to avoid WebGL GPU stalls)
56-
const TERMINAL_TAIL_SIZE = 256 * 1024; // 256KB tail for initial load
55+
const TERMINAL_CHUNK_SIZE = 32 * 1024; // 32KB chunks for terminal buffer loading
56+
const TERMINAL_TAIL_SIZE = 128 * 1024; // 128KB tail for initial load
5757
const SYNC_WAIT_TIMEOUT_MS = 50; // Wait timeout for terminal sync
5858
const STATS_POLLING_INTERVAL_MS = 2000; // System stats polling
5959

0 commit comments

Comments
 (0)