You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
serve: per-request instances are never disposed - MCP child processes accumulate until memory exhaustion #47727
opencode serve creates a new Instance for every API request that carries a ?directory= parameter, and these instances — along with the MCP server processes they spawn — are never disposed or garbage-collected. Under a client that polls several project directories (e.g. OpenChamber polling session status per directory), child processes accumulate unboundedly until the host exhausts memory.
Observed on a 31GB host: 43 registered project directories × 7 enabled global MCPs → ~900 MCP child processes (~110–200MB each) plus an ~8GB serve heap → 26.9GB RSS → swap thrash → all API requests time out. The log shows "creating instance" every 5–10 seconds continuously (21k+ lines over time), cycling the same directories.
Even after trimming to 2 enabled global MCPs and 26 directories, a freshly started serve accumulates ~185 MCP child processes within minutes — the accumulation itself doesn't stop, it only slows.
Watch the serve process tree: ps --ppid $(pgrep -f 'opencode serve') — the child count and RSS grow without bound; nothing is ever reaped.
Actual vs expected
Actual: one instance + full MCP fleet spawned per request cycle; processes accumulate forever → memory exhaustion.
Expected: instances reused per directory (or disposed when idle), and MCP child process trees terminated when an instance is disposed.
OpenCode version
1.18.29 (linuxbrew install)
Operating System
Linux x86_64 (Ubuntu), headless opencode serve behind the OpenChamber web/desktop client.
Additional context
Workarounds we had to apply locally: trimming global MCPs to the bare minimum, a systemd MemoryMax cap, a watchdog that restarts the service when MCP child count > 250 or cgroup memory > 20GB, and a nightly scheduled restart. Happy to share more logs/metrics if useful.
Memory Megathread #20695: Memory Megathread tracking memory-related issues including MCP process leaks.
If your issue is distinct (e.g., the per-?directory=-request instance creation path is a different code path from the reconnect leak in #46035), please clarify that in a comment — it would help maintainers track the two separately.
Second confirmation: macOS + OpenChamber, opencode serve 1.18.30 (homebrew)
Same accumulation on a different platform and client series. This is the per-request/per-connection instance path (#47727) and should be tracked separately from the browser-reconnect path (#46035), as requested.
Environment
macOS (Apple Silicon), node v24.19.0 (nvm)
opencode 1.18.30 (homebrew), launched by OpenChamber desktop as opencode serve --hostname 127.0.0.1 --port 50905
Global config (~/.config/opencode/opencode.jsonc) has exactly 2 local stdio MCP servers (openspec via npx -y openspec-mcp, gitnexus via gitnexus mcp) plus one remote
Live census (single serve PID, uptime ~4 min, idle, only one active chat)
serve holds 21 complete MCP sets, every process a direct child of serve (PPID = serve), zero orphans (no PPID 1), all launchers alive, one set per connection:
Total = 63 MCP child processes ≈ 4 GB, plus the serve process itself at ~940 MB → ~5 GB from 50+ node processes minutes after launching the client.
Spawns arrived in 3 bursts (28 @ 09:53:16, 20 @ 09:53:48, 6 @ 09:55:14): one round at startup warming sets, then further rounds per connection — matching an instance materialized per connection/request and never disposed.
This reproduces #47727 on stable 1.18.30 with a desktop/web client that polls directories (OpenChamber), on macOS, with no TUI client attached — sets keyed per (connection/directory instance), never reused, never reaped. Happy to attach full ps trees or lsof dumps if useful.
In this brand-new session the serve process already re-materialized MCP sets and RSS reached ~1.46 GB within ~12 min.
2 complete sets = 6 direct children (npm exec openspec → node openspec, gitnexus), zero orphans, all launchers alive. The 2nd set appeared later (etime 13:31 vs 13:48) → per-connection/request materialization.
Caught a transient extra node openspec-mcp spawn (2 MB) mid-census — accumulation was actively ongoing during sampling, not a one-time warm-up.
Symptom
One opencode serve process accumulates MCP child processes without bound until the machine swaps. Only local (stdio) MCPs leak; remote MCPs contribute no child processes.
It is per instance (directory), not per agent — correlates with repeated message="creating instance" directory=… lines; each instance initialises a full MCP fleet that is never disposed.
Not MCP-specific — reproduced identically with a stdio server and the same server behind mcp-remote, and with brave-search/airbnb we never touched.
OpenChamber 1.22.1 amplified it via an auto-reconnect plugin (fixed in 1.23.1 / OpenChamber#3350), but the leak persists on 1.23.2 + OpenCode 1.18.31.
No config knob (reuse / idle-timeout / max-instances / dispose) appears to exist.
Impact
Unbounded RSS + swap growth on an 18 GB machine; only manual reaping or restart helps. Happy to run diagnostics or test a patch.
The per-directory instance cache in packages/opencode/src/project/instance-store.ts was unbounded, so serve processes visiting many directories accumulated instances and their bootstrap resources (MCP children). A fix now bounds the cache to 16 instances, evicting the oldest completed entry and refreshing recency on access; in-flight boots are never evicted. Regression test included. The fix is on the branch of PR #48638.
Following the branch audits, the cache bound was reverted in 4551204c before merge: LRU eviction by load time can dispose an instance a long-running session still holds (no refcount), load awaiters are unprotected during concurrent eviction, a reload racing disposeContext can destroy the new generation's resources, and eviction can lead to a double dispose.
Requirements for re-landing: (1) in-use guard or refcount around provide, (2) awaiter protection during eviction, (3) serialize dispose vs reload per directory, (4) fix the double-dispose after eviction, (5) regression tests covering all four.
We're reverting the cache bound for now based on a full audit: evicting a completed entry can dispose a directory that is still in use (a long-running session holds the context without re-touching the store; once more than a bound's worth of directories have been loaded, the next load evicts it mid-flight), and the dispose path races reload() (async disposers can run against a freshly swapped entry, and a second identity guard can leave a dead cached entry).
To re-land safely it needs: an in-use refcount or lease around provide() and load awaiters, serialization between dispose and reload per directory, a double-dispose guard, and tests that evict while a borrower is active.
Confirming this on Windows v2.0.11 — accumulation also happens without any ?directory= polling, through plain MCP reconnects, and on Windows it has a very visible side effect.
Environment: OpenCode Desktop 2.0.11 (CLI opencode-cli.exe serve --service), Windows 11, 4 local stdio MCP servers configured via npx (@magicuidesign/mcp, @playwright/mcp, shadcn mcp, and codegraph serve --mcp). Normal interactive use, no opencode serve behind a polling client.
After a few hours, process counts:
cmd.exe 81
node.exe 105
opencode-cli.exe 1
Every one of those cmd.exe traces back to the single serve --service process. Grouping the 81 cmd.exe by their direct parent:
So each local MCP server costs twocmd.exe and twonode.exe, and the same four servers are alive up to 12 times concurrently. Nothing is ever reaped.
Two separate problems are visible in this one tree:
The leak — the process tree is not terminated on MCP reconnect/disconnect; the counts only grow.
Windows-visible console windows — those intermediates are console processes. Because they are never reaped they don't just flash for 40–150 ms, they stack up on the desktop as real persistent windows (81 of them, all titled cmd.exe), one per spawn attempt.
Getting anywhere near #47727's numbers doesn't even require a polling client on Windows — the churn alone gets you there. Happy to dump the full Win32_Process table (PID / ParentProcessId / CommandLine) if it helps.
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.
Activity
github-actions commentedon Sep 7, 2026
This issue might be a duplicate of existing issues. Please check:
opencode serveand are never reaped, leading to OOM. Very close match to your report.If your issue is distinct (e.g., the per-
?directory=-request instance creation path is a different code path from the reconnect leak in #46035), please clarify that in a comment — it would help maintainers track the two separately.sugarless-sweet commentedon Sep 12, 2026
Second confirmation: macOS + OpenChamber, opencode serve 1.18.30 (homebrew)
Same accumulation on a different platform and client series. This is the per-request/per-connection instance path (#47727) and should be tracked separately from the browser-reconnect path (#46035), as requested.
Environment
opencode serve --hostname 127.0.0.1 --port 50905~/.config/opencode/opencode.jsonc) has exactly 2 local stdio MCP servers (openspecvianpx -y openspec-mcp,gitnexusviagitnexus mcp) plus one remoteLive census (single serve PID, uptime ~4 min, idle, only one active chat)
npm exec openspec-mcp(wrapper, 41–65 MB) → 21 ×node …/openspec-mcp(30–54 MB)gitnexus mcp(node, 47–69 MB):50905; serve also keeps ~20 loopback connections to its own port (same self-loopback shape reported in serve (1.18.25): MCP child processes accumulate on web-client reconnects until the server OOMs #46035). Every MCP child keeps a live PIPE to serve — active processes, not zombies.This reproduces #47727 on stable 1.18.30 with a desktop/web client that polls directories (OpenChamber), on macOS, with no TUI client attached — sets keyed per (connection/directory instance), never reused, never reaped. Happy to attach full
pstrees orlsofdumps if useful.sugarless-sweet commentedon Sep 12, 2026
Full evidence bundle (ps trees, complete lsof dumps, network/set census) captured live on stable 1.18.30 / macOS / OpenChamber:
Evidence bundle: https://gist.github.com/sugarless-sweet/588effc909b71faa6d3e46c52a4e2c81
Highlights:
node openspec-mcpspawn (2 MB) mid-census — accumulation was actively ongoing during sampling, not a one-time warm-up.Full ready-to-attach reproduction data in the bundle; happy to add periodic samplings to show the growth curve if it helps.
test(server): add regression for MCP subprocess lifetime (anomalyco#4…
MCKLtech commentedon Sep 15, 2026
Adding my summary from debugging this issue on my own machine:
Environment
Mac15,7, Apple M3 Pro (12-core), 18 GB RAMoh-my-opencode-slimairbnb,brave-search,skills-radar; remote:crawl4ai,n8n,tregSymptom
One
opencode serveprocess accumulates MCP child processes without bound until the machine swaps. Only local (stdio) MCPs leak; remote MCPs contribute no child processes.Evidence (single
servepid, ~30 min session)brave-search: 14 → 48 → 66mcp-remote(stdio→HTTP bridge): 14 → 48 → 66custom mcp server: 7 → 33Key points
message="creating instance" directory=…lines; each instance initialises a full MCP fleet that is never disposed.mcp-remote, and withbrave-search/airbnbwe never touched.Impact
Unbounded RSS + swap growth on an 18 GB machine; only manual reaping or restart helps. Happy to run diagnostics or test a patch.
AceRothstein71 commentedon Sep 17, 2026
The per-directory instance cache in
packages/opencode/src/project/instance-store.tswas unbounded, so serve processes visiting many directories accumulated instances and their bootstrap resources (MCP children). A fix now bounds the cache to 16 instances, evicting the oldest completed entry and refreshing recency on access; in-flight boots are never evicted. Regression test included. The fix is on the branch of PR #48638.AceRothstein71 commentedon Sep 17, 2026
Following the branch audits, the cache bound was reverted in
4551204cbefore merge: LRU eviction by load time can dispose an instance a long-running session still holds (no refcount), load awaiters are unprotected during concurrent eviction, a reload racingdisposeContextcan destroy the new generation's resources, and eviction can lead to a double dispose.Requirements for re-landing: (1) in-use guard or refcount around
provide, (2) awaiter protection during eviction, (3) serialize dispose vs reload per directory, (4) fix the double-dispose after eviction, (5) regression tests covering all four.AceRothstein71 commentedon Sep 17, 2026
We're reverting the cache bound for now based on a full audit: evicting a completed entry can dispose a directory that is still in use (a long-running session holds the context without re-touching the store; once more than a bound's worth of directories have been loaded, the next load evicts it mid-flight), and the dispose path races
reload()(async disposers can run against a freshly swapped entry, and a second identity guard can leave a dead cached entry).To re-land safely it needs: an in-use refcount or lease around
provide()and load awaiters, serialization between dispose and reload per directory, a double-dispose guard, and tests that evict while a borrower is active.PVLPM commentedon Sep 20, 2026
Confirming this on Windows v2.0.11 — accumulation also happens without any
?directory=polling, through plain MCP reconnects, and on Windows it has a very visible side effect.Environment: OpenCode Desktop 2.0.11 (CLI
opencode-cli.exe serve --service), Windows 11, 4 local stdio MCP servers configured vianpx(@magicuidesign/mcp,@playwright/mcp,shadcn mcp, andcodegraph serve --mcp). Normal interactive use, noopencode servebehind a polling client.After a few hours, process counts:
Every one of those
cmd.exetraces back to the singleserve --serviceprocess. Grouping the 81cmd.exeby their direct parent:opencode-cli.execmd.exe /d /s /c "npx -y @magicuidesign/mcp@latest"opencode-cli.execmd.exe /d /s /c "npx -y @playwright/mcp@latest --config <path>"opencode-cli.execmd.exe /d /s /c "codegraph serve --mcp"opencode-cli.execmd.exe /d /s /c "npx shadcn@latest mcp"node.exe(npx-cli.js)cmd.exe /d /s /c <pkg-bin>So each local MCP server costs two
cmd.exeand twonode.exe, and the same four servers are alive up to 12 times concurrently. Nothing is ever reaped.Two separate problems are visible in this one tree:
cmd.exe), one per spawn attempt.Getting anywhere near #47727's numbers doesn't even require a polling client on Windows — the churn alone gets you there. Happy to dump the full
Win32_Processtable (PID / ParentProcessId / CommandLine) if it helps.thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊