High CPU usage (100%+) during LLM streaming in long sessions due to O(n) text buffer rendering #3870

Open
opened 2026-02-16 17:41:45 -05:00 by yindo · 19 comments
Owner

Originally created by @anntnzrb on GitHub (Dec 25, 2025).

Originally assigned to: @kommander on GitHub.

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:

  1. Recalculate virtual lines for ALL content on every update (not just viewport)
  2. Allocate new memory via mmap for every calculation (not reusing allocations)
  3. 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

  1. Cache virtual line calculations: Don't recalculate for unchanged content
  2. Virtualize to viewport: Only calculate virtual lines for visible rows
  3. Reuse allocations: Use object pooling instead of continuous mmap
  4. Batch updates during streaming: Coalesce rapid updates

For Session Memory Management

  1. Implement LRU eviction: As proposed in PR #1493
  2. Actually clear tool outputs on compaction: Don't just mark as compacted
  3. Stream large outputs: Don't buffer entire tool outputs in memory

For Rendering Pipeline

  1. Implement virtualized scrolling: As proposed in PR #3346
  2. Cache rendered blocks: Don't re-render unchanged message blocks
  3. Throttle re-renders during streaming: Limit to 30-60 FPS

OpenCode version

1.0.201

Steps to reproduce

  1. Start OpenCode in a project directory:

    bunx opencode-ai@latest
    
  2. 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
  3. Monitor system resources:

    while true; do ps -p <PID> -o pid,%cpu,%mem,rss; sleep 5; done
    
  4. 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 sample command
  • 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)

Originally created by @anntnzrb on GitHub (Dec 25, 2025). Originally assigned to: @kommander on GitHub. ### 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: 1. Recalculate virtual lines for ALL content on every update (not just viewport) 2. Allocate new memory via `mmap` for every calculation (not reusing allocations) 3. 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+ | --- <details> <summary><strong>Environment Details</strong></summary> ### 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 ```jsonc { "$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 } } ``` </details> <details> <summary><strong>Process Statistics Over Time</strong></summary> ### 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. </details> <details> <summary><strong>Memory Map Analysis (vmmap)</strong></summary> ``` 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 </details> <details> <summary><strong>CPU Sample Analysis (Full)</strong></summary> 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% | </details> <details> <summary><strong>Storage Analysis</strong></summary> ### 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 ```json { "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 </details> <details> <summary><strong>System Resource Impact</strong></summary> ### 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) ``` </details> <details> <summary><strong>Related Issues</strong></summary> ### 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 | </details> <details> <summary><strong>Related Pull Requests (Open)</strong></summary> 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 </details> <details> <summary><strong>Technical Recommendations</strong></summary> ### For OpenTUI Text Buffer 1. **Cache virtual line calculations**: Don't recalculate for unchanged content 2. **Virtualize to viewport**: Only calculate virtual lines for visible rows 3. **Reuse allocations**: Use object pooling instead of continuous mmap 4. **Batch updates during streaming**: Coalesce rapid updates ### For Session Memory Management 1. **Implement LRU eviction**: As proposed in PR #1493 2. **Actually clear tool outputs on compaction**: Don't just mark as compacted 3. **Stream large outputs**: Don't buffer entire tool outputs in memory ### For Rendering Pipeline 1. **Implement virtualized scrolling**: As proposed in PR #3346 2. **Cache rendered blocks**: Don't re-render unchanged message blocks 3. **Throttle re-renders during streaming**: Limit to 30-60 FPS </details> --- ### OpenCode version 1.0.201 ### Steps to reproduce 1. Start OpenCode in a project directory: ```bash bunx opencode-ai@latest ``` 2. 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 3. Monitor system resources: ```bash while true; do ps -p <PID> -o pid,%cpu,%mem,rss; sleep 5; done ``` 4. 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 `sample` command - 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)
yindo added the opentuiperf labels 2026-02-16 17:41:45 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Dec 25, 2025):

This issue might be a duplicate of existing issues. Please check:

  • #5220: Glob search uses 100% of CPU
  • #4818: Heavy CPU Usage and making my M1 mac laggy after updating
  • #5130: Mac unresponsive, significant lag
  • #3822: Output starts to slow to a crawl
  • #5363: opencode eating 70gb of memory?
  • #6119: TUI fails to render with critical memory leak (72.5G VIRT)
  • #4315: Memory stays unbounded: ACP session map + compacted tool outputs
  • #3013: Uses a huge amount of memory
  • #5700: Too high memory usage
  • #731: Critical Stability Issues: App Freezing & Hanging Analysis
  • #5361: TUI freezes for ~10 seconds periodically on WSL2
  • #4239: TUI hangs when shell outputs massive log

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Dec 25, 2025): This issue might be a duplicate of existing issues. Please check: - #5220: Glob search uses 100% of CPU - #4818: Heavy CPU Usage and making my M1 mac laggy after updating - #5130: Mac unresponsive, significant lag - #3822: Output starts to slow to a crawl - #5363: opencode eating 70gb of memory? - #6119: TUI fails to render with critical memory leak (72.5G VIRT) - #4315: Memory stays unbounded: ACP session map + compacted tool outputs - #3013: Uses a huge amount of memory - #5700: Too high memory usage - #731: Critical Stability Issues: App Freezing & Hanging Analysis - #5361: TUI freezes for ~10 seconds periodically on WSL2 - #4239: TUI hangs when shell outputs massive log Feel free to ignore if none of these address your specific case.
Author
Owner

@anntnzrb commented on GitHub (Dec 25, 2025):

New clues from current repo code (not seen in the issue text):

  1. The PRs referenced in the issue (#3346/#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 no packages/tui or .go sources; the active TUI is TS/Solid + OpenTUI under packages/opencode/src/cli/cmd/tui/.... So those optimizations can’t be applied directly without re-implementing the ideas for OpenTUI.

  2. Potential client-side memory leak in the OpenTUI TUI store: packages/opencode/src/cli/cmd/tui/context/sync.tsx caps store.message[sessionID] to 100 entries (it shift()s old messages), but it does not delete the corresponding store.part[messageID] entries when messages are dropped. Over long sessions this can grow the parts map even while messages are bounded.

  3. "Compaction doesn't reclaim memory" has a concrete mechanism in current server code: packages/opencode/src/session/compaction.ts marks old tool parts with part.state.time.compacted but does not clear part.state.output. packages/opencode/src/session/message-v2.ts only swaps in "[Old tool result content cleared]" at model-message conversion time when time.compacted is set, so the original output still lives in storage and can still be loaded/materialized.

  4. Related to large-output pressure: packages/opencode/src/tool/grep.ts currently 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.

@anntnzrb commented on GitHub (Dec 25, 2025): New clues from current repo code (not seen in the issue text): 1) The PRs referenced in the issue (#3346/#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 no `packages/tui` or `.go` sources; the active TUI is TS/Solid + OpenTUI under `packages/opencode/src/cli/cmd/tui/...`. So those optimizations can’t be applied directly without re-implementing the ideas for OpenTUI. 2) Potential client-side memory leak in the OpenTUI TUI store: `packages/opencode/src/cli/cmd/tui/context/sync.tsx` caps `store.message[sessionID]` to 100 entries (it `shift()`s old messages), but it does **not** delete the corresponding `store.part[messageID]` entries when messages are dropped. Over long sessions this can grow the parts map even while messages are bounded. 3) "Compaction doesn't reclaim memory" has a concrete mechanism in current server code: `packages/opencode/src/session/compaction.ts` marks old tool parts with `part.state.time.compacted` but does not clear `part.state.output`. `packages/opencode/src/session/message-v2.ts` only swaps in "[Old tool result content cleared]" at model-message conversion time when `time.compacted` is set, so the original output still lives in storage and can still be loaded/materialized. 4) Related to large-output pressure: `packages/opencode/src/tool/grep.ts` currently 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.
Author
Owner

@berenar commented on GitHub (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 /compact as much as possible instead. After a couple of hours in the same session, it becomes so slow that I need to Ctrl+C and open a fresh new session.
Exiting OpenCode and reattaching to the same session doesn't help.

My setup:

  • GitHub Copilot
  • Opus 4.5 / Sonnet 4.5
  • TUI
  • Tmux
  • Kitty
  • Apple M2 Pro, 16 gb, MacOS 26.1

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)

Image Image
@berenar commented on GitHub (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 `/compact` as much as possible instead. After a couple of hours in the same session, it becomes so slow that I need to `Ctrl+C` and open a fresh new session. Exiting OpenCode and reattaching to the same session doesn't help. My setup: - GitHub Copilot - Opus 4.5 / Sonnet 4.5 - TUI - Tmux - Kitty - Apple M2 Pro, 16 gb, MacOS 26.1 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) <img width="858" height="496" alt="Image" src="https://github.com/user-attachments/assets/a1933c61-3f56-4b94-b102-60bb4ca114bd" /> <img width="852" height="496" alt="Image" src="https://github.com/user-attachments/assets/76477790-b03f-4345-977b-98c1fe638b9d" />
Author
Owner

@obito commented on GitHub (Dec 27, 2025):

Image Image

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.

@obito commented on GitHub (Dec 27, 2025): <img width="930" height="128" alt="Image" src="https://github.com/user-attachments/assets/f0d8a78a-143e-4b07-8727-9bb8f37f5d8b" /> <img width="834" height="140" alt="Image" src="https://github.com/user-attachments/assets/c052d447-cadc-4514-bd1e-0119416d4217" /> 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.
Author
Owner

@fglogan commented on GitHub (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:

  1. Viewport-aware traversal in OpenTUI.
    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).
  2. Arena reuseThe allocator is hitting mmap on every frame. Should reset the arena at frame start instead of allocating fresh. Kills the syscall overhead.
    The TypeScript side (block virtualization, shimmer gating) would help too but the real win is in the Zig layer. PR #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?
@fglogan commented on GitHub (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: 1. Viewport-aware traversal in OpenTUI. 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). 2. Arena reuseThe allocator is hitting mmap on every frame. Should reset the arena at frame start instead of allocating fresh. Kills the syscall overhead. The TypeScript side (block virtualization, shimmer gating) would help too but the real win is in the Zig layer. PR #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?
Author
Owner

@kommander commented on GitHub (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.

@kommander commented on GitHub (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.
Author
Owner

@sepsi77 commented on GitHub (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.

@sepsi77 commented on GitHub (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.
Author
Owner

@kommander commented on GitHub (Jan 7, 2026):

Does it still accumulate that much in the latest version during a session?

@kommander commented on GitHub (Jan 7, 2026): Does it still accumulate that much in the latest version during a session?
Author
Owner

@sepsi77 commented on GitHub (Jan 7, 2026):

Image

@kommander yes, this is the current usage. I'm running version 1.1.4.

@sepsi77 commented on GitHub (Jan 7, 2026): <img width="733" height="200" alt="Image" src="https://github.com/user-attachments/assets/c56f199b-4081-4b9f-bdbb-ac337384d3dd" /> @kommander yes, this is the current usage. I'm running version 1.1.4.
Author
Owner

@kommander commented on GitHub (Jan 7, 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.

@kommander commented on GitHub (Jan 7, 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.
Author
Owner

@sepsi77 commented on GitHub (Jan 7, 2026):

@kommander so using /export and then email you the markdown file?

@sepsi77 commented on GitHub (Jan 7, 2026): @kommander so using /export and then email you the markdown file?
Author
Owner

@sepsi77 commented on GitHub (Jan 7, 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 commented on GitHub (Jan 7, 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/
Author
Owner

@sepsi77 commented on GitHub (Jan 7, 2026):

@kommander after nuking the storage folder memory usage seems to stay between 800 MB and 1 GB.

@sepsi77 commented on GitHub (Jan 7, 2026): @kommander after nuking the storage folder memory usage seems to stay between 800 MB and 1 GB.
Author
Owner

@mingodad commented on GitHub (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.

@mingodad commented on GitHub (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.
Author
Owner

@berenar commented on GitHub (Jan 8, 2026):

FYI I haven't had this issue for the past week, thank you so much for fixing it!

@berenar commented on GitHub (Jan 8, 2026): FYI I haven't had this issue for the past week, thank you so much for fixing it!
Author
Owner

@kommander commented on GitHub (Jan 8, 2026):

Cool! That begs the question, are others that still have the issue using any plugins/MCPs or forks?

@kommander commented on GitHub (Jan 8, 2026): Cool! That begs the question, are others that still have the issue using any plugins/MCPs or forks?
Author
Owner

@LeoMassie commented on GitHub (Jan 20, 2026):

This is still an issue for me on linux.

@LeoMassie commented on GitHub (Jan 20, 2026): This is still an issue for me on linux.
Author
Owner

@torrinworx commented on GitHub (Feb 13, 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:

Image
@torrinworx commented on GitHub (Feb 13, 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: <img width="2560" height="1599" alt="Image" src="https://github.com/user-attachments/assets/e19a66c2-eed2-4e11-8fef-6490218a7ee9" />
Author
Owner

@xKei5150 commented on GitHub (Feb 14, 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

@xKei5150 commented on GitHub (Feb 14, 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
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#3870