Repository navigation
High CPU usage (100%+) during LLM streaming in long sessions due to O(n) text buffer rendering #6172
Description
Description
OpenCode v1.0.201 exhibits sustained 100%+ CPU usage during active LLM streaming in long sessions, caused by an O(n) text buffer rendering loop in OpenTUI's native Zig renderer. The CPU drops to near-zero when idle, but memory accumulates and is not reclaimed even after session compaction. This makes large refactoring sessions extremely resource-intensive and can render the system sluggish.
Summary
| Metric | During Streaming | When Idle |
|---|---|---|
| CPU | 100-117% sustained | 0.1-0.5% |
| State | R+ (Running) | S+ (Sleeping) |
| Physical Footprint | 6.9 GB (peak 7.0 GB) | Remains high |
| RSS | 1.0-1.4 GB | 1.2 GB (not reclaimed) |
Root Cause
CPU samples show 100% of time spent in OpenTUI's text buffer renderer:
textBufferViewMeasureForDimensions
→ UnifiedTextBufferView.calculateVirtualLinesGeneric
→ rope.Rope.walkNode (recursive)
→ segment_callback
→ ArrayListAlignedUnmanaged.append
→ ArenaAllocator.alloc
→ PageAllocator.alloc
→ mmap (kernel)
This is in .7885f4ebfb6353d3-00000000.dylib - OpenTUI's native Zig renderer. The renderer appears to:
- Recalculate virtual lines for ALL content on every update (not just viewport)
- Allocate new memory via
mmapfor every calculation (not reusing allocations) - During streaming, this triggers expensive O(n) recalculation for every token
Session Context
The session was performing a large codebase refactoring:
| Metric | Value |
|---|---|
| Session Title | "Migrating from snowfall-lib to blueprint" |
| Files Modified | 198 |
| Lines Added | 3,565 |
| Lines Deleted | 833 |
| Session Parts | 12,248 |
| Session Messages | 530+ |
| Runtime | 1h 15m+ |
Environment Details
System Information
| Component | Value |
|---|---|
| OS | macOS 26.2 Tahoe (Build 25C56) |
| Kernel | Darwin 25.2.0 (xnu-12377.61.12~1/RELEASE_ARM64_T8132) |
| Chip | Apple M4 |
| CPU Cores | 10 (4 performance + 6 efficiency) |
| RAM | 16 GB |
| Architecture | arm64 |
OpenCode Environment
| Component | Value |
|---|---|
| OpenCode Version | 1.0.201 |
| Installation Method | bunx opencode-ai@latest |
| Bun Version | 1.3.2 |
| Shell | zsh |
| Terminal Multiplexer | tmux 3.5a |
| Terminal Type | tmux-256color |
Active Language Servers
| LSP | PID | CPU | Memory |
|---|---|---|---|
| bash-language-server | 35839 | 0% | 2.2% |
| nixd | 85540 | 0% | 0.2% |
| pyright | 86334 | 0% | 0.3% |
OpenCode Configuration
{
"$schema": "https://opencode.ai/config.json",
"tools": { "webfetch": false },
"permission": {
"edit": "allow",
"webfetch": "allow",
"bash": { "git push": "ask", "npm install": "ask", "*": "allow" }
},
"plugin": [
"opencode-openai-codex-auth@4.2.0",
"opencode-google-antigravity-auth"
],
"mcp": {
"exa": { "enabled": true, "type": "remote", "url": "https://mcp.exa.ai/mcp?..." },
"deepwiki": { "enabled": true, "type": "remote", "url": "https://mcp.deepwiki.com/mcp" }
},
"autoupdate": false,
"experimental": { "disable_paste_summary": true }
}Process Statistics Over Time
During Active Agent Work
| Timestamp | CPU % | MEM % | RSS | Physical Footprint | Elapsed | State |
|---|---|---|---|---|---|---|
| 17:57:54 | 109.5 | 8.4 | 1.4 GB | 5.5 GB | 00:00:00 | R+ |
| 18:04:13 | 102.2 | 7.4 | 1.2 GB | 6.1 GB | 01:04:01 | R+ |
| 18:09:52 | 113.7 | 8.0 | 1.3 GB | 6.7 GB | 01:12:01 | R+ |
| 18:12:37 | 100.9 | 6.3 | 1.0 GB | 6.9 GB | 01:14:48 | R+ |
After Agent Completed (Idle)
| Timestamp | CPU % | MEM % | RSS | Elapsed | State |
|---|---|---|---|---|---|
| 18:15:37 | 0.1 | 7.1 | 1.2 GB | 01:17:49 | S+ |
Key Observation: CPU dropped from ~110% to 0.1% once the agent finished, but memory (RSS 1.2 GB, 7.1% of 16GB) remained high and was not reclaimed.
Memory Map Analysis (vmmap)
Process: opencode [85447]
Physical footprint: 6.7G
Physical footprint (peak): 6.8G
ReadOnly portion of Libraries: Total=925.1M resident=214.1M(23%)
Writable regions: Total=63.5G written=6.6G(10%) resident=1.1G(2%) swapped_out=5.7G(9%)
REGION TYPE SIZE RESIDENT DIRTY SWAPPED REGIONS
=========== ======= ======== ===== ======= =======
VM_ALLOCATE 6.1G 612.7M 612.7M 5.4G 167013
WebKit Malloc 12.9G 354.4M 287.8M 57.4M 60
JS VM Gigacage 1.5G 39.9M 11.6M 144K 7
JS VM Gigacage (reserved) 50.5G 0K 0K 0K 2
IOAccelerator 640.2M 53.1M 53.1M 233.1M 426
JS JIT generated code 512.0M 15.7M 15.6M 8880K 3
__TEXT 334.5M 191.9M 0K 0K 342
=========== ======= ======== ===== ======= =======
TOTAL 77.7G 1.4G 1.0G 5.7G 169429
Key observations:
- VM_ALLOCATE: 6.1 GB virtual, 612.7 MB resident, 5.4 GB swapped - 167,013 regions
- WebKit Malloc: 12.9 GB virtual - JSC heap
- JS VM Gigacage: 52 GB reserved
CPU Sample Analysis (Full)
5-second sample showing the hot path:
Analysis of sampling opencode (pid 85447) every 1 millisecond
Process: opencode [85447]
Date/Time: 2025-12-25 18:09:51.760 -0500
Launch Time: 2025-12-25 16:57:48.086 -0500
OS Version: macOS 26.2 (25C56)
Physical footprint: 6.7G
Call graph:
4193 Thread_5448600 DispatchQueue_1: com.apple.main-thread (serial)
+ 4193 start (in dyld)
+ 4193 ??? (in opencode) [~15 frames of Bun internals]
+ 4193 ??? (in <unknown binary>) [JIT code]
+ 4182 ??? (in <unknown binary>)
+ 4143 ??? (in <unknown binary>)
+ 4142 ??? (in <unknown binary>)
+ 3812 ??? (in <unknown binary>)
+ 3809 ??? (in <unknown binary>)
+ 3767 ??? (in <unknown binary>) [0x263aec94954]
+ 3767 textBufferViewMeasureForDimensions + 256
+ 3767 UnifiedTextBufferView.calculateVirtualLinesGeneric + 444
+ 3767 rope.Rope.walkNode + 144
+ 3767 rope.Rope.walkNode + 172
+ 3767 walkLinesAndSegments.WalkContext.walker + 156
+ 3767 calculateVirtualLinesGeneric.WrapContext.segment_callback + 624
+ 3767 ArrayListAlignedUnmanaged.append + 356
+ 3767 ArenaAllocator.alloc + 120
+ 3767 PageAllocator.alloc + 136
+ 3767 mmap + 80
+ 3767 __mmap + 8
Hot Path Breakdown:
| Function | Library | Samples | % |
|---|---|---|---|
__mmap |
libsystem_kernel.dylib | 3767 | 89.8% |
PageAllocator.alloc |
OpenTUI (.dylib) | 3767 | 89.8% |
ArenaAllocator.alloc |
OpenTUI (.dylib) | 3767 | 89.8% |
ArrayListAlignedUnmanaged.append |
OpenTUI (.dylib) | 3767 | 89.8% |
segment_callback |
OpenTUI (.dylib) | 3767 | 89.8% |
rope.Rope.walkNode |
OpenTUI (.dylib) | 3767 | 89.8% |
calculateVirtualLinesGeneric |
OpenTUI (.dylib) | 3767 | 89.8% |
textBufferViewMeasureForDimensions |
OpenTUI (.dylib) | 3767 | 89.8% |
Storage Analysis
Global OpenCode Storage
~/.local/share/opencode/
├── bin/ 325 MB (LSP servers)
├── log/ 608 KB (log files)
├── snapshot/ 485 MB (git snapshots)
├── storage/ 385 MB (session data)
│ ├── message/ 81 MB (531 directories)
│ ├── part/ 283 MB (12,248 directories)
│ ├── session_diff/ ~17 MB (498 files)
Current Session
{
"id": "ses_4a87a33a1ffee9ZS0Sir0p7Akd",
"version": "1.0.201",
"title": "Migrating from snowfall-lib to blueprint",
"summary": {
"additions": 3565,
"deletions": 833,
"files": 198
}
}- Session diff: 420 KB, 199 JSON entries, 1,394 lines
System Resource Impact
Memory Pressure
Swap I/O:
Swapins: 4470468
Swapouts: 8129927
Compressor Stats:
Pages used by compressor: 254459
Pages decompressed: 78905151
Pages compressed: 133605383
System-wide memory free percentage: 60%
Swap Usage
vm.swapusage: total = 1024.00M used = 156.88M free = 867.12M (encrypted)
Load Average
Load Avg: 2.46, 2.82, 2.95 (on 10-core system)
Related Issues
High CPU / Performance Issues
| Issue | Title | Status |
|---|---|---|
| #5220 | Glob search uses 100% of CPU | OPEN |
| #4818 | Heavy CPU Usage and making my M1 mac laggy after updating | OPEN |
| #5130 | Mac unresponsive, significant lag | OPEN |
| #3822 | Output starts to slow to a crawl | OPEN |
Memory Issues
| Issue | Title | Status |
|---|---|---|
| #5363 | opencode eating 70gb of memory? | OPEN |
| #4315 | Memory stays unbounded: ACP session map + compacted tool outputs | OPEN |
| #3013 | Uses a huge amount of memory | OPEN |
| #6119 | TUI fails to render with critical memory leak (72.5G VIRT) | OPEN |
| #5700 | Too high memory usage | OPEN |
TUI Freeze / Hang Issues
| Issue | Title | Status |
|---|---|---|
| #731 | Critical Stability Issues: App Freezing & Hanging Analysis | OPEN |
| #5361 | TUI freezes for ~10 seconds periodically on WSL2 | OPEN |
| #4239 | TUI hangs when shell outputs massive log | OPEN |
Related Pull Requests (Open)
These PRs appear to directly address the root cause:
| PR | Title | Description |
|---|---|---|
| #3346 | perf(tui/chat): virtualize message rendering to remove O(n) reflows | Virtualizes chat blocks, caches per-block content, binary search for visible blocks |
| #2867 | perf(tui): Eliminate O(n) scans from render hot path | Local shimmer detection, header caching, virtual viewport rendering |
| #1493 | fix: implement LRU cache for session memory management | LRU eviction, TTL expiration, memory pressure handling |
| #4963 | feat/perf: add pagination to modified files in sidebar | Pagination for large file lists |
| #5432 | fix(grep): stream ripgrep output to prevent memory exhaustion | Stream instead of buffer |
Key PR: #3346 - Virtualize Message Rendering
Changes:
- Build per-block caches once, use binary search for visible blocks
- Shimmer gating: Only animate when in-flight, re-render only when at bottom and backlog ≤ 2000 lines
Impact:
- Streaming remains smooth on large histories
- Rendering work scales with viewport, not backlog
Key PR: #2867 - Eliminate O(n) Scans
Optimizations:
- Local shimmer detection - O(parts-in-message) instead of O(all-parts)
- Virtual viewport rendering - O(1) scrolling regardless of message count
- Fast selection updates - 10-50x speedup
Performance:
- Before: ~3-10 FPS with 100+ messages
- After: Smooth 60 FPS
Key PR: #1493 - LRU Cache for Session Memory
Features:
- LRU eviction at capacity
- TTL-based expiration
- Memory pressure handling (aggressive cleanup >80%)
Impact:
- 50-80% reduction in memory for long sessions
Technical Recommendations
For OpenTUI Text Buffer
- Cache virtual line calculations: Don't recalculate for unchanged content
- Virtualize to viewport: Only calculate virtual lines for visible rows
- Reuse allocations: Use object pooling instead of continuous mmap
- Batch updates during streaming: Coalesce rapid updates
For Session Memory Management
- Implement LRU eviction: As proposed in PR fix: implement LRU cache for session memory management #1493
- Actually clear tool outputs on compaction: Don't just mark as compacted
- Stream large outputs: Don't buffer entire tool outputs in memory
For Rendering Pipeline
- Implement virtualized scrolling: As proposed in PR perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming #3346
- Cache rendered blocks: Don't re-render unchanged message blocks
- Throttle re-renders during streaming: Limit to 30-60 FPS
OpenCode version
1.0.201
Steps to reproduce
-
Start OpenCode in a project directory:
bunx opencode-ai@latest
-
Begin a large refactoring task that:
- Involves many files (100+ file modifications)
- Generates substantial LLM output
- Runs for an extended period (30+ minutes)
- Uses tools that produce verbose output
-
Monitor system resources:
while true; do ps -p <PID> -o pid,%cpu,%mem,rss; sleep 5; done
-
Observe:
- CPU spikes to 100%+ during LLM streaming
- Memory accumulates and doesn't decrease after compaction
- System becomes sluggish
Screenshot and/or share link
Share link: https://opncd.ai/share/XmViOCAJ
Diagnostic data provided above instead of screenshots - includes:
- Full CPU samples from macOS
samplecommand - Memory maps from
vmmap - Heap analysis from
heap - Process statistics over time
Operating System
macOS 26.2 (Tahoe, Build 25C56) - Apple M4, 16GB RAM
Terminal
tmux 3.5a + zsh (TERM=tmux-256color)
Activity
github-actions commentedon Dec 25, 2025
This issue might be a duplicate of existing issues. Please check:
Feel free to ignore if none of these address your specific case.
anntnzrb commentedon Dec 25, 2025
New clues from current repo code (not seen in the issue text):
The PRs referenced in the issue (perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming #3346/perf(tui): Eliminate O(n) scans from render hot path for smooth animations #2867) appear to target the old Go TUI codepath (they mention files like
packages/tui/internal/components/chat/messages.go). This repo/branch currently has nopackages/tuior.gosources; the active TUI is TS/Solid + OpenTUI underpackages/opencode/src/cli/cmd/tui/.... So those optimizations can’t be applied directly without re-implementing the ideas for OpenTUI.Potential client-side memory leak in the OpenTUI TUI store:
packages/opencode/src/cli/cmd/tui/context/sync.tsxcapsstore.message[sessionID]to 100 entries (itshift()s old messages), but it does not delete the correspondingstore.part[messageID]entries when messages are dropped. Over long sessions this can grow the parts map even while messages are bounded."Compaction doesn't reclaim memory" has a concrete mechanism in current server code:
packages/opencode/src/session/compaction.tsmarks old tool parts withpart.state.time.compactedbut does not clearpart.state.output.packages/opencode/src/session/message-v2.tsonly swaps in "[Old tool result content cleared]" at model-message conversion time whentime.compactedis set, so the original output still lives in storage and can still be loaded/materialized.Related to large-output pressure:
packages/opencode/src/tool/grep.tscurrently reads all ripgrep stdout into a string then splits + stats + sorts before truncating to 100. On huge result sets this is a worst-case memory/CPU spike and can indirectly worsen TUI rendering pressure during long sessions.berenar commentedon Dec 26, 2025
Yes, I encounter this issue frequently. I use GitHub Copilot as a provider, which charges for premium requests per session created. Therefore, I try to avoid creating new sessions and use
/compactas much as possible instead. After a couple of hours in the same session, it becomes so slow that I need toCtrl+Cand open a fresh new session.Exiting OpenCode and reattaching to the same session doesn't help.
My setup:
Update: yes I think this issue description makes sense with what I'm experiencing. During Plan, when it's very long (and the session is long too, I think), the response streaming keeps getting slower and slower, to the point it almost freezes OpenCode (the small animation too). And the output is only black and white, with no colors. so maybe it's an issue with the syntax highlighting 🕵️♂️. When switching to Build, it's not that laggy anymore (only when scrolling up in history)
obito commentedon Dec 27, 2025
Almost 13gb of RAM being consumed from a (very very) large session. I think I hold the record! It's very irresponsive, to the point where when it asks me for permission to run tools, I just hit enter and switch to something else until it register my keyboard entry (it takes around 5-10s).
Sometimes the screen becomes black, then come back. Also, the response of the LLM is truncated or missing sometimes, before a tool call or at the end of the text in Plan mode.
fglogan commentedon Dec 30, 2025
I have 3 OC Instances running on MacOS Sonoma x86 older laptop with 16GB RAM - OC taking 13.2GB RAM and locking up computer.
My project is in Rust but I looked into the issue a little deeper because I'm working on an edge device app and this is a no-go--but I have not touched zig so I'm wary..
Took a look at this.
--> The v1.0.200 scroll jank fix is different - that was TypeScript side. This is in the Zig renderer.
calculateVirtualLinesGeneric
→ rope.Rope.walkNode
→ ArenaAllocator.alloc
→ mmap
Every streaming token triggers a full rope walk. With 12k session parts that's O(n) work 30-60x per second.
Two things need to happen:
Right now calculateVirtualLinesGeneric walks from root. It should take viewport bounds and binary search to the start position, then stop when it passes the end. Changes O(n) to O(log n + visible rows).
The TypeScript side (block virtualization, shimmer gating) would help too but the real win is in the Zig layer. PR perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming #3346 had the right idea for the TS side but that was before the rewrite.
Is there an OpenTUI repo where this should be filed separately, or is it vendored?
bench(text-buffer-view): add measureForDimensions benchmark
kommander commentedon Jan 5, 2026
The biggest memory hog should be solved in the latest version with the fixes around session filtering on the server and not loading all full sessions in the TUI. Some more text rendering performance fixes incoming with an opentui upgrade.
sepsi77 commentedon Jan 7, 2026
I noticed that exiting opencode and then reopening the same session again seems to drop memory usage drastically. I went from 7GB RAM usage by OpenCode to 780MB. Just by /exit > reopen > /sessions > select the last session > continue.
kommander commentedon Jan 7, 2026
Does it still accumulate that much in the latest version during a session?
sepsi77 commentedon Jan 8, 2026
@kommander yes, this is the current usage. I'm running version 1.1.4.
kommander commentedon Jan 8, 2026
Oh wow, I rarely cross 1gb over a day. Would you be able to share a session that causes so much memory usage so I can analyze? You can also send it to my GitHub email if you want to keep it private.
sepsi77 commentedon Jan 8, 2026
@kommander so using /export and then email you the markdown file?
sepsi77 commentedon Jan 8, 2026
@kommander here's the session export MD file.
https://drive.google.com/file/d/14ofKYxz6D185aYX57G42yGXw06cO1ha4/view?usp=drive_link
For some reason this starts at the middle of the last session. After my previous message (with screenshot showing 4.1 GB of memory usage) I existed, opened OC again and reloaded the last session. The export is a part of that. I'm not sure why it doesn't include my first message within that session. But perhaps it shows the usage pattern. Heavy tool use and running tests, linters (ruff) and type checking (basedpyright). Also uses sub-agents often.
At the end of this relatively short session memory usage sits at 2.3 GB.
My .local/share/opencode folder starts to get pretty big also. I'm wondering if this could contribute.
seppo@LMA-VNF44C opencode % du -sh */
353M bin/
4.5M log/
318M snapshot/
1.5G storage/
seppo@LMA-VNF44C opencode % cd storage
seppo@LMA-VNF44C storage % du -sh */
282M message/
1.1G part/
48K plugin/
52K project/
2.9M session/
111M session_diff/
8.0K session_share/
492K todo/
sepsi77 commentedon Jan 8, 2026
@kommander after nuking the storage folder memory usage seems to stay between 800 MB and 1 GB.
mingodad commentedon Jan 8, 2026
I'm having the same problem and after starting again after removing the '.local/share/opencode' folder I'm back to normal memory usage.
Looking at the sessions in my case it seems that it was caused by the llm doing a test for debug using printf and it was filling the logs.
berenar commentedon Jan 8, 2026
FYI I haven't had this issue for the past week, thank you so much for fixing it!
kommander commentedon Jan 8, 2026
Cool! That begs the question, are others that still have the issue using any plugins/MCPs or forks?
shitastical commentedon Jan 20, 2026
This is still an issue for me on linux.
torrinworx commentedon Feb 14, 2026
Can this issue please get fixed? I'm experiencing this as well on the latest version, seeing all my cores maxed at 100% usage when idle. Not even streaming text from a model. The drops are when I switch to another window on my ubuntu desktop:
xKei5150 commentedon Feb 15, 2026
I'm also experiencing this on long sessions. Almost no response from the models and just stuck loading. The /compact seems to work for now to mitigate the issue. The /handoff command also wont work once this happens
torrinworx commentedon Feb 18, 2026
From a little bit of experimenting it seems like this is actually an issue with Vscode's terminal render loop. And it's not limited to OpenCode.
OpenCode running with normal cpu usage in an Terminator terminal:

Btop running in the vscode terminal also exhibits high cpu usage:
I'm on:
github-actions commentedon Apr 20, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.