Skip to content

Multiple memory leaks cause unbounded RAM growth during extended TUI usage #16697

Closed

Description

@binarydoubling

Description

During extended usage, OpenCode exhibits unbounded memory growth from multiple independent sources. This has been independently reported and investigated by 20+ contributors, resulting in a massive wave of PRs that collectively address the full scope of the problem.

Scope of Memory Leaks

In-Memory Leaks (Runtime)

Category Source Impact PR(s)
TUI event listeners sdk.event.on() in app.tsx, session route, prompt — never unsubscribed Listeners accumulate on every navigation/re-render #16695, #8254, #9147
Unbounded Maps/Sets LSP diagnostics, Bus subscriptions, RPC listeners — grow without eviction Monotonic memory growth over session lifetime #16695, #12259
Timer/interval leaks Models refresh interval, leader timeout, state disposal timeout — never cleared Closures + captured state retained indefinitely #16695, #9148
Session data retention Sync store never frees previous session's messages/parts/diffs on switch Memory scales with number of sessions visited #16695, #10392
Plugin subscriber stacking Bus.subscribeAll() called without storing unsubscribe handle Each init() stacks another permanent wildcard subscriber #16346
Message array retention Full msgs array stays alive during processor.process() Large arrays pinned in memory unnecessarily #16346
Subagent deallocation Subagent sessions never deallocated after task completion GB-scale growth in long-running sessions #14650, #15435
Event-reducer accumulation Message delta strings accumulate without cap Unbounded string growth #14650
SSE stream leaks SSE connections not properly cleaned up on disconnect Stale connections hold memory #15646, #9146
Process/Bus cleanup Child processes and Bus subscriptions not cleaned on instance disposal Resources retained after they're needed #15646, #13186, #9149
Subscription disposal No dispose functions for Bus subscriptions Permanent listeners on long-lived instances #7914, #7032
Tool output retention Tool output and attachments not cleared when pruning Dead data accumulates #7049
OAuth transport leaks MCP OAuth transports accumulate without timeout Abandoned flows hold memory #9145
LSP stderr buffering LSP server stderr not consumed, grows unbounded Memory growth per LSP server #16241
Zombie processes Missing cleanup handlers leave child processes running Process accumulation over time #13186, #15757
MCP server accumulation Idle MCP server instances never disposed Process count grows indefinitely #15326
Instance disposal No idle-timeout disposal in serve mode Instances live forever #16616

Database & Storage Leaks

Category Source Impact PR(s)
No data retention Sessions spanning 53+ days, never auto-cleaned DB grew to 1.99 GB #16730
auto_vacuum = OFF Deleted data never reclaims disk space SQLite file only grows #16730
Unbounded WAL No periodic WAL checkpoint WAL file grows indefinitely #16730
Oversized cache_size 64 MB fixed allocation Excessive for embedded tool #16730
Startup cleanup No maintenance/cleanup on startup Stale data persists across restarts #16628

Real-World Impact

All Related PRs

Over 20 open PRs addressing memory leaks, spanning January–March 2026:

Core memory leak fixes

PR Author Focus Files
#16695 @binarydoubling TUI listeners, Maps/Sets eviction, timers, session cleanup 16
#16730 @BYK SQLite config, database bloat, data retention —
#16346 @GooseG17 Plugin subscriber stacking, msgs retention, LSP 5
#14650 @kryptobaseddev Subagent deallocation, event-reducer caps 14
#15646 @brendandebeasi SSE streams, Bus disposal, process cleanup 11
#15435 @tda1017 Consolidation of #14650 and #8953 24
#13594 @feelsodev 6 memory leaks for long-running sessions 6
#13186 @dzianisv Memory leaks and zombie processes 16
#10392 @ztripez Session handling memory leaks 15
#13514 @sebishogun Multiple memory leaks (with Windows test fixes) 1

Targeted fixes

PR Author Focus Files
#16616 @sjawhar Idle-timeout Instance disposal for serve mode 2
#16241 @dimaosipa LSP stderr unbounded growth 2
#16628 @jmylchreest Startup cleanup and maintenance system 6
#15326 @lytedev Dispose idle MCP server instances 2
#12259 @Stranmor TTL, LRU eviction, ring buffers 4
#9146 @sauerdaniel Session event stream leaks (ACP) 2
#9147 @sauerdaniel Event listener leaks in TUI 3
#9148 @sauerdaniel Timeout, interval, subscription cleanup 3
#9145 @sauerdaniel OAuth transport and process cleanup 2
#9149 @sauerdaniel Instance disposal chain for plugins/caches 6
#8254 @sauerdaniel SDK event listener cleanup on unmount 1
#7914 @hendem Subscription disposal functions 10
#7049 @hendem Tool output/attachment cleanup on prune 2

Monitoring & diagnostics

PR Author Focus Files
#13194 @goniz Memory usage monitoring/leak detection 6

Related Issues

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 9, 2026
  2. binarydoubling commented on Mar 9, 2026

    @binarydoubling
    Author

    I went through every open memory leak PR on this repo. What I found is, frankly, unacceptable.

    23 open PRs. 20+ contributors. 0 merged. 63 days and counting.

    The community has been screaming about this since January. Here's what's been sitting unreviewed:

    @hendem submitted Bus subscription disposal fixes on January 6th. Had to rebase and resubmit on Jan 12th because the PR rotted. Got 30 review comments but still never merged. 63 days and counting.

    @sauerdaniel opened 6 PRs in a single day on January 17th. SDK event listener cleanup (#8254), OAuth transport leaks (#9145), ACP session cleanup (#9146), TUI event listener leaks (#9147), timeout/interval cleanup (#9148), and Instance disposal chain (#9149). Every single one: 0 reviews, 0 comments. 52 days of complete silence.

    @ztripez submitted a 15-file mega-fix on January 24th covering LRU caches, prompt loop cloning, pending timeouts, and more (#10392). 0 reviews, 0 comments. 45 days.

    @Stranmor added TTL and LRU eviction across ACP sessions, Instance cache, LSP clients, and PTY ring buffers on February 5th (#12259). 0 reviews, 0 comments. 32 days.

    @dzianisv reported 100GB+ RAM leading to a macOS crash from O(n²) string concatenation in processor.ts on February 11th (#13186). 0 reviews, 0 comments. 26 days.

    @sebishogun, @feelsodev, @kryptobaseddev, @lytedev, @tda1017, @brendandebeasi, @NachoFLizaur, @dimaosipa, @GooseG17, @sjawhar, @jmylchreest, @BYK all submitted fixes between February and March. Almost all of them: 0 reviews, 0 comments.

    15 out of 23 PRs have literally zero engagement from maintainers. No reviews. No comments. No "thanks, we'll look at this." No "this conflicts with internal work." Nothing.

    Because nothing gets merged, contributors keep independently discovering and fixing the same bugs. lsp/client.ts has been independently fixed 7 times across 7 different PRs (#13594, #14650, #15435, #15646, #16346, #16695, #16730). session/prompt.ts, also 7 times. bus/index.ts, 6 times. tool/bash.ts, 5 times. plugin/index.ts, 5 times. app.tsx, 4 times. That's 20+ people writing the same fixes for the same bugs because the first person's fix was never reviewed.

    Meanwhile, users are reporting:

    People are having their operating systems crash. Their laptops are thermal throttling. They're restarting opencode every couple hours as a workaround for bugs that were fixed by the community months ago.

    The fixes exist. All 23 of them. They just need to be merged.

    A maintainer needs to triage these PRs, identify the best version of each fix (since many overlap), coordinate the rebasing, and start shipping. The longer this waits, the worse the merge conflicts get, and the more contributors give up and walk away.

    Related PRs for reference: #7032, #7049, #7914, #8254, #9145, #9146, #9147, #9148, #9149, #10392, #12259, #13186, #13194, #13514, #13594, #14650, #15326, #15435, #15646, #15757, #16241, #16346, #16616, #16628, #16695, #16730

  3. rothnic commented on Mar 9, 2026

    @rothnic

    I'm not sure why I finally decided to search around about this, but probably because things have gotten kind of out of hand lately? I can't go very long without my laptop, with 32GB of ram, and nothing running but a browser (min tabs open) and ghostty (with memory bug fix installed) becomes unusable.

    I'm running multiple projects in parallel and often will notice things really slow down when the used ram exceeds what is available and I assume it is paging. I have seen ~60GB+ of usage multiple times, which I don't have.

    I do wonder at time how much of this is related to agents spawning processes, like development servers, or tests which aren't cleaned up. I've tried some of the process management plugins, but overall haven't had good luck. It is promising to see that there are many fixes though, because this is nearly unusable.

  4. binarydoubling commented on Mar 9, 2026

    @binarydoubling
    Author

    Yeah, it's really frustrating. I imagine the maintainers have way too much on their plate, this project grew EXPONENTIALLY in size and now it's just an amalgamation of conflicting issues, PRs, and bugs that they can't reasonably get to in time. And what makes it worse is that this isn't exactly a small project, it's something I've used extensively myself and nothing else quite matches the DX here. I genuinely love OpenCode and think it's arguably the best general-purpose coding agent out there but the amount of memory leaks in here are just.. it's unacceptable and unwieldy. People in the Discord talk about it, too, it's just getting buried underneath the devs' internal roadmap and more pressing bugs/feature releases. This needs to get prioritized though, cuz stuff like this turns power users away, HARD.

  5. binarydoubling commented on Mar 9, 2026

    @binarydoubling
    Author

    Consolidated fix PR: #16695

    I reviewed every one of the 23+ open PRs listed in this issue, analyzed the diffs, identified overlapping fixes, and consolidated the best version of each into a single PR: #16695 (branch fix/memory-leaks).

    Coverage: 30 files, 3 commits

    The PR addresses the following categories from the issue table:

    Category Status Details
    TUI event listeners ✅ Fixed onCleanup for all sdk.event.on() calls in app.tsx, prompt, session route
    Unbounded Maps/Sets ✅ Fixed Bus/RPC empty entry deletion, diagnostics LRU cap (200), open-files cap (1000), LSP index teardown
    Timer/interval leaks ✅ Fixed Leader timeout, models refresh, state disposal timeout, theme SIGUSR2
    Session data retention ✅ Fixed Sync store frees previous session data, clearSession() on Permission/Question/FileTime
    Plugin subscriber stacking ✅ Fixed unsub?.() before re-subscribing in Plugin.init()
    Message array retention ✅ Fixed Event queue capped at 1000
    Subagent deallocation ✅ Fixed Session.remove() after task tool completion
    SSE stream leaks ✅ Fixed Centralized cleanup() with done guard on all 3 SSE endpoints
    Process/Bus cleanup ✅ Fixed Instance.disposeAll() with 5s timeout on exit, serve cmd graceful shutdown
    Subscription disposal ✅ Fixed Format, ShareNext, Plugin all store and cleanup unsub handles
    Tool output retention ✅ Fixed output = "", attachments = undefined on compaction prune
    OAuth transport leaks ✅ Fixed Close existing transport before replacing
    Bash tool output ✅ Fixed Chunks array with 10MB ring buffer instead of O(n²) +=

    Items left for follow-up PRs

    These are valuable but architecturally significant enough to warrant separate review:

    The goal was to ship the highest-impact, lowest-risk fixes in one shot. The remaining items are individually reviewable without blocking on this PR.

    cc @binarydoubling @sauerdaniel @hendem @ztripez @dzianisv @kryptobaseddev @brendandebeasi @GooseG17 — thank you all for the incredible work identifying and fixing these. This PR wouldn't exist without your contributions.

  6. NachoFLizaur commented on Mar 9, 2026

    @NachoFLizaur
    Contributor

    hi @binarydoubling, thanks for the incredible work you've done consolidating these! I haven't seen the PR that I did (#15757) mentioned in the one you've done (#16695), have I missed it or did you not include it in there?

  7. binarydoubling commented on Mar 9, 2026

    @binarydoubling
    Author

    @NachoFLizaur Great catch — just added your PR #15757 (robust process exit detection) to the consolidated branch! It's now in commit 36b93df on PR #16695.

    Included:

    • Polling watchdog in bash tool + Process.spawn to detect exit when Bun misses events
    • Process registry (active map) with stale()/reap() exports for server-level cleanup
    • Shell.killTree improvements with alive() helper and proper SIGKILL escalation
    • Session-level watchdog interval in the prompt loop

    You're credited as co-author on the commit. Thank you again.

  8. twoGiants commented on Mar 10, 2026

    @twoGiants

    @binarydoubling Thank you for this! I see I am not the only one struggling with memory leak issues. This issue an PR needs to be pushed to the top!

    Hi @thdxr ! Can you please take a look or ask a colleague to do so? Thank you!

  9. geier commented on Mar 13, 2026

    @geier

    This PR helps a lot with the memory leak issues for me. It doesn't completely fix it yet, but it's a lot better then before.

  10. dzianisv commented on Mar 15, 2026

    @dzianisv

    macOS kernel panic caused by opencode memory pressure — field evidence (2026-03-15)

    Environment

    • macOS 26.x, Apple Silicon, 16 GB RAM
    • opencode running in serve mode with OPENCODE_MEMORY_MONITOR=1 and threshold at 5120 MB
    • Multiple MCP servers enabled: chrome-devtools-mcp, whisper-mcp, playwriter, @modelcontextprotocol/server-memory

    What happened

    Machine hard-rebooted at 07:33 due to a kernel watchdog panic:

    watchdog timeout: no checkins from watchdogd in 90 seconds
    Compressor Info: LOW swap space
    Panicked task ... pid 68489: assistantd
    

    Evidence from opencode memory monitor

    The built-in memory monitor (memory-serve-*.ndjson) captured hourly growth before the crash:

    Hour (UTC) opencode RSS (max) Process tree RSS (max) Child peak Process count Active sessions
    23:00 1,315 MB 8,472 MB 8,109 MB 55 212
    00:00 1,606 MB 7,783 MB 7,531 MB 48 224
    01:00 1,714 MB 5,376 MB 4,858 MB 42 238

    Sampling stopped at 01:17 UTC — the opencode serve process either died or hung. There is a 6+ hour gap with no samples before the 07:33 reboot. The in-process setInterval sampler cannot survive the monitored process dying.

    Root causes observed

    1. 238 active sessions accumulated — sessions were never archived or cleaned up. Each retains message data, parts, diffs, and PTY/Bus subscriptions in memory.

    2. MCP child-process fanout — the process tree had 42-55 children. Top consumers from threshold snapshots:

      • chrome-devtools-mcp (npm exec)
      • whisper-mcp (npm exec)
      • playwriter (npm exec)
      • @modelcontextprotocol/server-memory
      • Multiple bun workers (198-224 MB each)
    3. No idle MCP server disposal — MCP server instances accumulated and were never disposed when idle (fix(web): dispose idle MCP server instances to prevent process accumulation #15326).

    4. No session count cap — nothing prevents hundreds of active sessions from accumulating in serve mode.

    Key insight: child-process memory dominates

    opencode root process RSS was "only" ~1.7 GB — the vast majority of memory (6-7 GB) was in child processes. Fixes targeting only the JS heap (event listeners, sync store, etc.) are necessary but not sufficient. Child-process lifecycle management is the bigger lever for serve mode.

    Suggested improvements for serve mode

    1. Session count cap / idle archival — archive sessions idle for N minutes, especially in serve mode.
    2. MCP server idle timeout — dispose MCP server child processes after configurable idle period (fix(web): dispose idle MCP server instances to prevent process accumulation #15326 / fix: add idle-timeout Instance disposal for serve mode memory #16616).
    3. Active child-process budget — if tree RSS exceeds a configurable limit, aggressively dispose idle instances and MCP servers before swap death spiral.
    4. Out-of-process memory watchdog — a small external sampler (shell script under launchd/systemd) that logs ps tree RSS independently, surviving opencode crashes.

    This corroborates the issue perfectly — the leaks are real, compounding, and cause kernel panics on macOS. The consolidated fix PR #16695 addresses the in-process JS leaks, but child-process lifecycle issues (#15326, #16616, #13186) are equally critical for serve mode stability.

  11. dzianisv commented on Mar 15, 2026

    @dzianisv

    Live reproduction: chrome-devtools-mcp leaks to 1.7 GB in < 2 hours (2026-03-15)

    Caught this in real-time on the same machine that panicked overnight.

    Current process snapshot

    opencode serve has been running for ~2 hours. Current MCP child-process state:

    PID RSS Process
    4928 1,661 MB chrome-devtools-mcp --autoConnect
    4991 40 MB chrome-devtools-mcp --browserUrl http://127.0.0.1:9222
    4949 54 MB playwriter
    4970 30 MB whisper-mcp
    4910 49 MB mcp-server-memory

    The chrome-devtools-mcp process (PID 4928) grew from ~103 MB to 1,661 MB in under 2 hours. At this rate it would hit 8+ GB overnight — exactly matching last night's crash trajectory.

    Additionally, there are two chrome-devtools-mcp instances running (PIDs 4928 and 4991). The --autoConnect one is the leak; the --browserUrl one stays small. This is likely a duplicate spawn from the shared map not deduplicating correctly across instances, or the config enabling it under two names.

    What this confirms

    1. The overnight crash root cause is chrome-devtools-mcp — not opencode's JS heap. opencode serve itself is at 366 MB (PID 4388), while a single MCP child is 4.5x larger and growing.

    2. MCP server idle disposal is critical — the shared Map in mcp/index.ts has no idle timeout. Once connected, MCP clients live forever regardless of whether any session is actively using them.

    3. The leak is in chrome-devtools-mcp itself, not opencode — but opencode's lack of idle disposal means the leaking process is never terminated. Even a well-behaved host should terminate idle MCP servers to bound total memory.

    Suggested fix priorities

    1. MCP idle timeout — add lastUsed timestamp to the shared map and a periodic sweep that closes entries with refs === 0 after a configurable idle period (default 10 min). We have a local patch for this.
    2. Upstream chrome-devtools-mcp bug report — the --autoConnect mode leaks ~13 MB/min. This needs to be reported to the chrome-devtools-mcp maintainers separately.
    3. MCP process-tree RSS budget — opencode should monitor child-process RSS and forcibly terminate MCP servers that exceed a configurable limit, regardless of idle status.

    Relation to overnight panic

    Last night's hourly data showed process-tree RSS peaking at 8,472 MB at 23:00 with 55 child processes. The dominant contributor was chrome-devtools-mcp. Today's live data shows the same process growing at the same rate. Without the MCP idle disposal fix, this will crash the machine again tonight.

  12. 11 remaining items

  13. sjawhar commented on Mar 27, 2026

    @sjawhar

    Memory leak forensics: 187GB RSS, SSE AsyncQueue as root cause

    Hey @thdxr — I know you've been asking for heap snapshots. Unfortunately we couldn't capture one from this incident (the telemetry sampler wasn't wired into TUI mode, and the 10GB RSS guard blocked it once we tried manually). But we did capture extensive forensic data that traces the leak to a specific code path.

    Environment

    Timeline

    Time Event
    T+0m Process starts, bootstraps normally
    T+16m First string or blob too big rejection (Bun string size limit)
    T+16-28m 28 more string or blob too big errors
    T+28-54m 26-minute gap — process mostly silent, churning memory
    T+54m First Out of memory rejection
    T+106m Process killed (187GB RSS, 8.7GB swap)

    Root cause trace

    packages/opencode/src/server/routes/event.ts:64-65:

    const unsub = Bus.subscribeAll((event) => {
      q.push(JSON.stringify(event))
    })

    The AsyncQueue in packages/opencode/src/util/queue.ts has no size limit — it's just a bare T[] = []. Every bus event gets JSON.stringify'd and pushed. If the SSE client (browser tab) falls behind:

    1. session.updated events carry full session state (DB fetch every time via projector)
    2. session.diff events carry full file before/after strings (megabytes per event for large diffs)
    3. Events accumulate in the queue without bound
    4. JSON.stringify() on large events eventually hits string or blob too big
    5. Queue + retained strings exhaust all memory

    This matches #15645 (SSE streams leak on disconnect) and #17908 (serve mode OOM).

    Evidence captured

    • 57MB activity log recovered from deleted-but-open file descriptors
    • 11MB /proc/smaps showing 194GB of anonymous mmap'd memory (Bun heap arenas)
    • 3-second strace: 1,615 sched_yield + 1,044 futex calls (heavy thread contention), madvise(MADV_DODUMP) on 3.6GB in 6 calls
    • Service activity: permission service logged 5,555 entries, tool.registry 3,182 entries — consistent with heavy agentic tool use
    • Full forensic report available if useful

    Fix (PR incoming)

    Adding backpressure to AsyncQueue — configurable max capacity with drop-oldest behavior, plus slow-client detection in the SSE endpoint to disconnect clients that fall too far behind.

    Also fixing the telemetry gap: wiring startMemoryTelemetry() into the TUI entry point so future incidents produce heap snapshots.

  14. sjawhar commented on Mar 27, 2026

    @sjawhar

    Follow-up: branches available for upstream

    Based on the forensic analysis above, we've implemented fixes and have them on individual branches against dev. Would any of these be useful contributions?

    Root cause fix — SSE backpressure:

    Memory telemetry (SIGUSR1 heap dump tooling):

    • feat/memory-telemetry — Periodic memory sampling (RSS, heap, JSC object counts) with SIGUSR1→heap snapshot and SIGUSR2→one-shot memory sample to stderr. Wired into all entry points including TUI. No more "we can't get heap dumps" — any running OpenCode process responds to kill -USR1 <pid>.

    Bug fixes:

    Happy to open PRs for whichever of these look useful. The SSE backpressure + memory telemetry pair seems highest impact given the heap dump gap you've described.

  15. sjawhar commented on Mar 28, 2026

    @sjawhar

    Heap snapshot analysis: closure/instance leak in serve mode

    Follow-up to our earlier forensic report. We captured heap snapshots from serve processes and found a second, distinct leak (separate from the SSE AsyncQueue issue).

    Setup: Two opencode serve snapshots compared — one fresh (18s old, 313MB snapshot) vs one after ~2 hours of session accumulation (886MB snapshot).

    Top-level delta:

    Type Fresh Leaky Delta Multiplier
    string 409 MB (1.25M) 1,836 MB (2.69M) +1,427 MB 2.1x
    closure 54 MB (1.74M) 105 MB (3.43M) +51 MB 2.0x
    object 81 MB (373K) 150 MB (1.07M) +68 MB 2.9x
    code 54 MB (542K) 77 MB (967K) +23 MB 1.8x

    The smoking gun — per-instance closures all show exactly +100:

    Closure name Fresh Leaky Note
    closeSessionById 64 164 +100
    deleteSession 64 164 +100
    trackTask 64 164 +100
    cleanupOldSessions 64 164 +100
    scheduleIdleNotification 64 164 +100
    markSessionActivity 64 164 +100
    getOrCreateState 65 165 +100
    unsubscribe 31 131 +100 (4.2x)
    loop 176 573 +397 (3.3x)

    100 InstanceState instances were created but never disposed. Each leaked instance retains:

    • Bus subscriptions (the unsubscribe closures at 4.2x)
    • Background loops (loop at 3.3x)
    • Session lifecycle closures
    • ~14K Effect.js composition closures (Function/AsyncFunction)

    Root cause hypothesis: InstanceState disposal in serve mode isn't firing for idle sessions. The idle timeout system (scheduleIdleNotification) creates instances but cleanupOldSessions doesn't actually trigger disposal — bus subscriptions survive, background loops continue, closures accumulate.

    This is proportional to total sessions handled. Our crash investigation found 1,000+ sessions in a single day flowing through one serve process. At ~14K closures per instance, that's 14M+ orphaned closures.

    Heap snapshots available at:

    • Leaky: 886MB, 9.4M nodes, port 13381 after 2hr session accumulation
    • Fresh baseline: 313MB, 4.5M nodes, port 13382 at 18s uptime

    Happy to upload these somewhere if useful. We also have the memory telemetry time series showing the growth pattern (Function/Object/JSLexicalEnvironment accumulation at ~3-5GB/hr).

  16. sjawhar commented on Mar 28, 2026

    @sjawhar

    Root cause confirmed: per-instance bus subscriptions never cleaned up

    Digging deeper into the heap snapshots, I traced the closure leak to specific bus subscriptions that are created per-instance but never unsubscribed:

    5 subscriptions leak per instance in InstanceBootstrap():

    Subscription File Line Returns unsub? Unsub called?
    Session.Event.Updated share-next.ts 68 Yes (ignored) ❌ Never
    MessageV2.Event.Updated share-next.ts 78 Yes (ignored) ❌ Never
    MessageV2.Event.PartUpdated share-next.ts 95 Yes (ignored) ❌ Never
    Session.Event.Diff share-next.ts 103 Yes (ignored) ❌ Never
    Command.Event.Executed bootstrap.ts 26 Yes (ignored) ❌ Never

    ShareNext.init() is called from InstanceBootstrap() which runs per-instance. Each call adds 4 global bus subscriptions. The Bus.subscribe() return value (the unsubscribe function) is never captured or called.

    Combined with no idle disposal (instances cached indefinitely in Instance.cache with no timeout mechanism), this means:

    • N instances created → 5N orphaned subscriptions → 5N × closures in heap
    • Each closure captures callback + PubSub scope + subscription state
    • Each instance also brings ~14K Effect.js composition closures

    Compare with properly-scoped subscriptions (these DON'T leak):

    • vcs.ts:162 — uses Effect.forkScoped ✅
    • plugin/index.ts:152 — uses Effect.forkScoped ✅
    • mcp.ts:233 — manual unsub in finally block ✅
    • lsp/client.ts:219 — manual unsub in finally block ✅

    Fix approach: Either tie ShareNext.init() and InstanceBootstrap() subscriptions to instance lifecycle (via InstanceState disposer or explicit cleanup), or guard against duplicate subscriptions.

  17. sjawhar commented on Mar 28, 2026

    @sjawhar

    Rebased + opened upstream PRs against dev from sjawhar fork branches:\n\n- fix/sse-backpressure: https://github.com/anomalyco/opencode/pull/19493\n- feat/memory-telemetry: https://github.com/anomalyco/opencode/pull/19494\n- fix/tui-worker-hang: https://github.com/anomalyco/opencode/pull/19496\n- fix/stream-drain-watchdog: https://github.com/anomalyco/opencode/pull/19495\n- fix/plugin-dist-tag-update: https://github.com/anomalyco/opencode/pull/19497\n\nAll five branches were rebased onto latest upstream dev and pushed from the fork.

  18. sjawhar commented on Apr 1, 2026

    @sjawhar

    This is incredibly valuable forensic memory profiling work, @sjawhar. The trace data on the SSE queues and orphaned closures looks very solid. Looking forward to reviewing the PRs you've opened.

    I'm sorry for the noise. When I asked the agent to share the data, I didn't realize it was making such bold and confident claims.

    And I've actually had to delete and consolidate some of my PRs because maintaining them all separately became a pain in the ass, and I'm still actually fighting the memory leaks. And some TUI threading issue, I think. A bit over my head here, oping to dig myself out of this hole at some point.

  19. thdxr commented on Apr 2, 2026

    @thdxr
    Member

    Closing in favor of #20695 where we're collecting all memory-related problems. Please follow that issue for updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions