Resumed long sessions render all messages as React components, causing 10s+ keypress lag
Description
Resuming a long-lived session causes the TUI to become completely unusable — 10+ second delay per keypress, 7.5 GB RSS, 110% CPU. The root cause is the M21 message rendering component rendering every message in history as a React/Ink component without virtualization.
Root Cause Analysis
The message list component (M21 / v8H in the minified bundle) builds a renderable array from the full conversation:
// M21 component (deminified)
let z = useMemo(() => [...normalizedMessageHistory, ...normalize(messages).filter(gs)], [messages, normalizedMessageHistory])
In transcript mode, there's a limit: OdH = 10 — only the last 10 messages render. But in the normal prompt screen (d = false), no limit applies:
let MH = d ? AH.slice(-OdH) : AH // d=false in prompt mode → MH = AH (all messages)
Then every message gets a React element:
v.map(($H, AH) => createElement(ycD, { key:..., message: $H, ... }))
On resume, normalizedMessageHistory is populated from the full JSONL session log. With a long session, this means thousands of React components rendered for messages that aren't even visible on screen.
Evidence
Session stats (session af1bd7fd, project wow-ui-sim):
- 35 MB JSONL file, 5,716 lines
- 1,174 assistant messages, 720 user messages, 696 tool use blocks
- 5 context compactions (session continued multiple times)
- ~1,894 renderable message components
Process behavior:
- 110% CPU, 7.5 GB RSS on resume
perfprofile: dominated bymadvise→unmap_page_range→clear_page_erms→flush_tlb_mm_range(V8 GC thrashing)strace: 66%restart_syscall, 20%futex, 12%sched_yield— busy-wait from GC pressure- Keypress latency: ~10 seconds (completely unusable)
Comparison with normal session:
| | Normal session | Slow session |
|---|---|---|
| JSONL size | 0.5 MB | 35 MB |
| Messages | 138 | 1,894 |
| Tool uses | ~20 | 696 |
| RSS | 500 MB | 7,500 MB |
| CPU idle | 40% | 110% |
| Keypress lag | instant | ~10s |
Proposed Fix
Apply the same OdH = 10 limit (or similar) to the prompt screen, not just transcript mode. Messages scrolled off the terminal aren't visible anyway. Alternatively, virtualize the message list to only render what's in the viewport.
Steps to Reproduce
- Use a session heavily over many hours with many tool calls (or find/create a large JSONL session file)
- Exit the session
- Resume with
claude --resume <session-id> - Try typing — observe multi-second keypress lag
Environment
- Claude Code 2.1.39
- Linux (Arch), AMD Ryzen AI 9 HX PRO 370 (24 threads, 54 GB RAM)
- Terminal: Ghostty
Related Issues
- #19934 — same symptoms on resume, reporter saw 40 GB memory, had 10 GB conversation file
- #22968 — gradual degradation in long sessions (same root cause, slower onset)
- #17142 — idle CPU from sched_yield/futex (related idle spinning)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗