[BUG] UI freeze (120s+) due to unbounded screen buffer rendering on long sessions (recover from uninteded close)
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?
Claude Code CLI becomes completely unresponsive — frozen UI, no cursor blink, all keyboard input ignored including Ctrl+C and Esc — during sessions with accumulated conversation history. The freeze lasts from 17 seconds to over 2 minutes, scaling with conversation length. In the worst case (--continue on a long session), the freeze is indefinite and requires process kill.
Debug log analysis points to synchronous full-screen re-rendering of an unbounded virtual screen buffer (observed up to 1,632 rows / 116 KB per frame) blocking the Node.js event loop.
Key evidence from debug logs:
- The virtual screen buffer grows monotonically with conversation length (15 → 1,632 rows observed)
- Every render is a full redraw:
blit=0, 100.0% writes(zero incremental/differential rendering) - The final render frame before a forced kill wrote 116 KB in a single frame (
screen=1632x120) - During freezes, SSD is saturated but CPU cores are idle (I/O-bound, not CPU-bound)
- Ctrl+C does not work because the event loop is blocked and cannot dispatch signals
What Should Happen?
- The UI should remain responsive during and after loading conversation history
- Ctrl+C should always work (signal handling should not depend on render completion)
- Screen rendering should be viewport-limited (only render the visible ~40 terminal rows, not 1,632 rows)
--continueon a long session should load without freezing
Error Messages/Logs
No error messages — the process silently freezes. The debug log shows a gap (no output at all) during the freeze.
**120s freeze in a fresh session** (debug file `f26ae9db`):
2026-02-07T17:27:28.191Z [DEBUG] Forked agent [prompt_suggestion] finished: 1 messages, types=[assistant], totalUsage: input=3 output=20 cacheRead=47960 cacheCreate=592
^^^ 120 SECONDS OF COMPLETE SILENCE IN DEBUG LOG ^^^
2026-02-07T17:29:27.883Z [DEBUG] LSP Diagnostics: getLSPDiagnosticAttachments called
2026-02-07T17:29:28.305Z [DEBUG] Getting matching hook commands for UserPromptSubmit with query: undefined
2026-02-07T17:29:28.324Z [DEBUG] High write ratio: blit=0, write=1065 (100.0% writes), screen=36x120
**Final frame before kill on `--continue` session** (debug file `bf001f6e`):
2026-02-07T17:25:22.203Z [DEBUG] High write ratio: blit=0, write=116313 (100.0% writes), screen=1632x120
2026-02-07T17:25:22.528Z [DEBUG] [API:request] Creating client, ANTHROPIC_CUSTOM_HEADERS present: false, has Authorization header: false
2026-02-07T17:25:22.528Z [DEBUG] [API:auth] OAuth token check starting
2026-02-07T17:25:22.528Z [DEBUG] [API:auth] OAuth token check complete
^^^ DEBUG LOG ENDS HERE (process killed by user) ^^^
**Screen buffer growth within a single session** (session f26ae9db):
17:27:00 screen= 15x120 write= 907 B (MCP init)
17:29:28 screen= 36x120 write= 1,065 B (post-freeze)
17:29:50 screen= 56x120 write= 2,056 B
17:30:10 screen= 74x120 write= 2,689 B
17:30:20 screen=102x120 write= 3,617 B
17:30:21 screen=161x120 write= 4,749 B
17:30:34 screen=172x120 write= 5,144 B
... unbounded growth continues ...
bf001f6e screen=1632x120 write=116,313 B (killed session)
**All freeze gaps end with the same pattern**: silence → `LSP Diagnostics: getLSPDiagnosticAttachments called` → normal activity resumes. This is consistent with a blocked event loop: timer callbacks (LSP diagnostics) are the first to fire when the loop unblocks.
Steps to Reproduce
Confirmed scenario (indefinite freeze, requires kill):
- Use Claude Code heavily in a single project over multiple sessions (this environment has 265 session files totaling 1.6 GB)
- Run a session with 100+ messages and multiple MCP servers active (4 in this case: Serena, Cipher, Tavily, Playwright)
- Exit the session normally
- Run
claude --continue - Wait for MCP initialization to complete (~7 s)
- Observe: UI freezes completely — no cursor blink, no response to any keyboard input including Ctrl+C and Esc
- Only recovery is to kill the process externally
Also observed (temporary freeze, self-resolves):
- Start a fresh session (no
--continue) in a project with many accumulated sessions - After startup, the UI freezes for ~120 s before accepting input
- During the session, additional freezes (17–61 s) occur during response streaming, with duration scaling with message count
Reproducibility: The --continue freeze was confirmed across 3 consecutive attempts, requiring kill each time.
Workaround: Start fresh sessions without --continue. Shorter freezes during streaming are unavoidable but self-resolve.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.34
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
Environment Details
| Item | Value |
|------|-------|
| CPU | Intel Core i5-14500 (14 physical / 20 logical cores, none saturated during freeze) |
| RAM | 64 GB |
| Storage | NVMe SSD (saturated during freeze — continuous r/w band visible in Task Manager) |
| Node.js | v24.4.1 |
| MCP servers | 4 active: Serena, Cipher, Tavily, Playwright |
| Deferred tools | 52 registered (0 loaded at freeze time) |
| Permission rules | 285 total (88 user + 197 local; local rules = 51,221 chars in a single debug log line) |
| Conversation storage | 1.6 GB across 265 session files |
| Largest session files | 391 MB, 171 MB, 137 MB, 101 MB, 89 MB |
Root Cause Analysis
Primary: Synchronous full-screen re-rendering blocks the event loop
- Claude Code uses Ink (React for CLI) to render the terminal UI
- The virtual screen buffer grows with conversation length (observed: 15 → 1,632 rows)
- Every re-render writes the ENTIRE buffer to stdout (
blit=0, no differential rendering) - A 116 KB synchronous write blocks the Node.js event loop
- While blocked: signals are not processed (Ctrl+C ignored), input is not read, timers do not fire
Why --continue makes it worse:--continue loads full conversation history and renders it immediately, creating a large screen buffer from the start. Fresh sessions build up gradually, giving the user brief usable windows between progressively longer stalls.
Why SSD is saturated but CPU is not:
The bottleneck is I/O. Large terminal writes (up to 116 KB/frame) combined with .claude.json persistence and JSONL appends saturate the storage subsystem.
Contributing factors:
blit=0consistently — the diff algorithm finds nothing to reuse, possibly due to cursor animation or status bar changes invalidating the entire buffer- 285 permission rules (51 KB single log line) — unclear if this affects the render path
- 265 accumulated session files (1.6 GB) — may slow session index operations
Freeze Gaps Observed Across Sessions
| Session | Gap | Screen rows | Write bytes | Trigger |
|---------|-----|-------------|-------------|---------|
| f26ae9db (fresh) | 120 s | 36 (after) | 1,065 | prompt_suggestion completion |
| f26ae9db (fresh) | 61 s | — | — | Post-response render |
| f26ae9db (fresh) | 22 s | — | — | Stream start (msg 79→80) |
| f26ae9db (fresh) | 20 s | — | — | Stream start (msg 169→170) |
| f26ae9db (fresh) | 17 s | — | — | Stream start (msg 135→136) |
| bf001f6e (normal use) | 155 s | — | — | Mid-session |
| bf001f6e (normal use) | 132 s | — | — | Mid-session |
| bf001f6e (normal use) | 72 s | 852 | 29,171 | Mid-session |
| bf001f6e (--continue) | indefinite | 1,632 | 116,313 | Killed by user |
Suggested Improvements
- Viewport-only rendering: Only render the visible terminal viewport (~40 rows), not 1,632 rows
- Incremental rendering: Investigate why
blit=0— stable history should be diffable - Chunked/async terminal writes: Yield to event loop between write chunks to keep Ctrl+C responsive
--continuelazy rendering: Load data but render only the tail; collapse older messages- Screen buffer size cap: Hard-limit at ~500 rows; virtualize beyond that
- Loading feedback: Show a progress indicator if any phase takes >2 s
Data Available for Engineering
Debug log files and session JSONL files are available on request for cross-referencing with server-side logs. Session IDs referenced in this report:
f26ae9db-6637-4e79-9b4a-20ebef40fc2b(fresh session with 120 s freeze)bf001f6e-bfcb-447f-a3a6-b2e3b44754c4(long session,--continueindefinite freeze)5febb5f9-f71e-492f-8cdf-b9a547ec411e(killed after 4 s)0e32f6ea-357e-48d9-883a-499336560464(killed after 8 s)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗