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: RSS grows with activity and remains elevated while idle #51340
On OpenCode v2.0.16, long-running opencode serve processes grow in RSS during active use and remain at the higher level while idle. Fresh instances start at 161–166 MB; one heavily used instance grew from 161 MB to ~1.0 GB over ~7 hours. A separate mostly idle instance stayed near 300 MB for 4.5 days. This appears tied to usage rather than continuous growth at idle.
In one synthetic Code Mode test, a single execute call made three rounds of ~76 MB logical allocations (arrays and strings). The serve process rose from 976 MB to 1,708 MB RSS within a minute and remained near that level for 30 idle minutes. Deleting 15 sessions through the serve API did not materially lower RSS (±6 MB). Restarting the server restores the lower baseline. These are RSS observations, not proof that the allocations are still reachable on the JS heap.
Expected: Transient workload should not leave the long-running server at a substantially higher resident-memory baseline indefinitely, or the expected retention/limits should be documented.
Steps to reproduce
Start the standalone binary with opencode serve --hostname 127.0.0.1 --port <port> and record the serve PID's RSS (for example, ps -o rss= -p <pid>; output is in KiB).
Connect a client and run a Code Mode execute call with several rounds of large temporary array/string allocations (~76 MB logical allocation per round in the observation above).
Record RSS immediately afterward, then leave the serve process idle for 30 minutes and record RSS again. Optionally delete completed sessions through the serve API and recheck.
OpenCode version
v2.0.16 (standalone Bun-compiled binary)
Operating System
macOS 26, arm64
Additional context
Possible contributors from code inspection at 2.0 commit 34aa427 (not confirmed root causes): pruning marks tool parts compacted without clearing state.output (packages/opencode/src/session/compaction.ts); server event fan-out uses PubSub.unbounded() (packages/core/src/event.ts); Code Mode runs in-process without a memory limit in ExecutionLimits (packages/codemode/src/interpreter/runtime.ts, packages/codemode/src/codemode.ts). Related reports: #36677, #33213, #45215, #7421. More measurements and code excerpts were included in the original issue revision and the comment above.
Long-running opencode serve (v2.0.16, macOS arm64) shows usage-driven memory retention that never gets returned: fresh serves start at ~161-166 MB RSS, idle instances stay flat for days (~300 MB after 4.5 days), but active use raises RSS in permanent steps - a heavily-used instance climbed 161 MB -> ~1.0 GB in 7 h, and a synthetic Code Mode test (3 rounds x ~76 MB allocations in one execute) spiked the serve process by +732 MB (~3.2x amplification) with zero reclamation over 30 idle minutes. Deleting sessions via API doesn't help; recovery is restart-only.
Code Mode has no heap ceiling - codemode/interpreter/runtime.ts runs in-process with limits only for timeout/tool-calls/output-bytes, so heavy allocations land directly in the serve heap.
Full details and measurements in the issue body above.
changed the title [-]serve: RSS grows in permanent steps with usage and is never returned (prune keeps tool payloads; unbounded event queues; in-process Code Mode without heap ceiling)[/-][+]serve: RSS grows with activity and remains elevated while idle[/+]on Sep 25, 2026
Same signature here, but at larger scale and with heap-snapshot data: a SIGUSR1 snapshot at a 23 GB RSS plateau showed only 172 MB of live JS (134:1), with ~21.7 GB sitting committed-but-empty in the JSC allocator region — so the elevation looks like native allocator non-decommit (cf. oven-sh/bun#21560) fueled by per-turn history decode churn, not retained app state. We also identified the churn sources (per-turn full-history JSON decode, git snapshot storms, a 5-min models.dev re-emission wave with no content-digest skip). Full evidence in #52041 — happy to compare readings with your case to see if it's one issue or two.
Thanks for the pointer. I retested this on v2.0.23 (macOS arm64, isolated standalone serve), and it looks like #51761 and this report are different issues.
Retest
Fresh serve baseline: 164.0 MiB RSS
After one Code Mode execute with 3 rounds of ~76 MiB temporary array/string churn: 995.3 MiB (+831.3 MiB)
Idle 1 minute: 995.3 MiB
Idle 10 minutes: 997.0 MiB
Idle 30 minutes: 1003.6 MiB
Deleted the test session through the API (204): 1003.7 MiB after another 10 seconds
I also ran a cold-start control with no tool call:
Fresh serve: 162.7 MiB
After one ordinary model turn: 446.2 MiB (+283.5 MiB)
After 1 idle minute: 446.7 MiB
So the Code Mode stress run ended roughly 549 MiB above the no-tool control, and there was no meaningful RSS reclamation over 30 idle minutes.
This is still only an RSS observation; it does not prove that the temporary JS objects remain reachable. The pages may have been freed internally but retained by the Bun/JSC allocator. Operationally, however, the serve RSS high-water behavior reported here is still reproducible.
#51761 was a runaway allocation/OOM in the TUI, and its associated fix #52233 updated OpenTUI to 0.5.13. That can fix the TUI issue without changing the serve/Code Mode allocator behavior measured here.
So unfortunately: no, this specific case is not solved for me on v2.0.23; it appears to be a separate issue.
Thanks for the detailed measurements. I tried to reproduce this on v2.0.16 but on Linux x64, since macOS isn't available here, and the memory came back down.
Setup: a simulated model had the server run Code Mode execute with three rounds of large temporary array/string allocations per call (roughly 60–80 MB each). I sampled the serve process RSS with ps -o rss=.
One call: 253 MB at start, 627 MB right after, then 270 MB after 30 s idle and about 224 MB after 2 minutes.
Six calls in a row in one session: stayed between 597 and 685 MB with no climbing trend, then dropped to 272 MB after 30 s idle and 234 MB after 90 s.
On Linux, then, the memory from this workload is freed and returned to the OS. Nothing stays retained between calls. Your numbers come from macOS arm64, where the system allocator may keep freed pages counted in RSS. That would look like retention even if nothing on the JS heap is still reachable.
These would help narrow it down:
On the elevated, idle macOS process, the JS heap usage next to the RSS (for example, the heap figures from a heap snapshot or Bun's heap stats, if you can collect them), so we can tell real retention from allocator caching.
Whether RSS goes down on macOS if you put the system under memory pressure, or after several more minutes of idling.
Whether the same workload on Linux (or in a Linux container) shows the growth for you.
Whether it still happens on the latest release (bun add -g @opencode/cli@latest).
You can reply here with the details, or edit the issue and then leave a comment saying you've updated it.
I'm closing this for now, since we haven't heard back. I couldn't reproduce the memory growth on Linux with v2.0.16. After the same kind of large Code Mode execute allocations, the serve process RSS went back to its starting level within a minute or two of idling. We couldn't tell real retention on macOS apart from the allocator keeping freed pages, so we couldn't take it further.
If it still happens for you, please comment here or open a new issue with:
the JS heap usage of the idle, elevated process next to its RSS
whether RSS drops under memory pressure or after a longer idle
whether it also happens on the latest release
We can reopen and look again then. Thanks for the detailed report!
Reopening with the data from the closing comment. Short version: it still happens on the latest release (v2.2.5), idle RSS does not come back, and the vmmap/footprint numbers now split "real retention" from "allocator keeps freed pages" — the RSS inflation is mostly the latter, with a smaller real-dirty part that also stays.
Setup
opencode v2.2.5, official opencode-darwin-arm64.zip release binary, macOS 26.6.2, arm64
Workload: one agent turn driven by a local mock OpenAI-compatible provider whose response forces a single Code Mode execute call with 3 rounds of ~76 MB temporary allocations (40 MB string + ~36 MB number array per round, released between rounds) — same shape as the original report
RSS sampled every 15 s via ps -o rss=; footprint via footprint(1); regions via vmmap -summary
1. Allocator numbers next to RSS
measurement
RSS
phys footprint
mimalloc (MemTag 240) resident / dirty
fresh serve (control instance)
156.2 MiB
74.9 MiB
70.3 / 61.8 MiB
repro serve, fresh baseline
153.3 MiB
—
—
repro serve, right after the turn
410.7 MiB
190.8 MiB (peak 308.6)
287.1 / 159.4 MiB
repro serve, after 16 min idle
622.9 MiB
189.7 MiB (peak 363.8)
450.3 / 136.4 MiB
Of the 623 MiB idle RSS, only ~190 MiB is private dirty (footprint). The mimalloc region alone holds 450 MiB resident with 136 MiB dirty, so ~314 MiB of RSS is freed-but-still-resident allocator pages. JSC itself stays tiny (Gigacage dirty ~5 MiB, JIT ~19 MiB). I could not pull an in-process JS heap snapshot from the compiled release binary (Bun.generateHeapSnapshot() needs in-process access and serve exposes no such endpoint); #52041's SIGUSR1 snapshot (172 MB live JS at a 23 GB plateau) is the JSC-level datapoint and matches this shape.
The same pattern in the field: the OpenChamber-managed serve on this machine (v2.2.0, up 3 days, mostly idle) sits at 1.70 GiB RSS but only 520 MiB phys footprint (peak 991 MiB), with Memory Tag 240 resident 1.4 GiB / dirty 394 MiB.
2. Longer idle
Isolated v2.2.5, 16 minutes idle after the turn: RSS never dropped. It stepped 411 → 424 MiB around 18:18 and 424 → 638 MiB at 18:23 local (61,536 KiB in one step). The isolated server log pins the second step to the background models.dev catalog refresh — provider.updated / model.updated events at 10:23:09Z with no user activity — matching the "models.dev re-emission wave" described in serve: RSS ratchets to 25-32 GB under concurrent agent turns; heap snapshot shows only 172 MB live JS (allocator-side retention) #52041. Timeline (KiB): 420,816 flat for ~9 min → 638,016 → flat to the end.
Field: the same v2.2.0 serve was 1.48 GiB RSS on Oct 29 (~18 h uptime) and 1.70 GiB on Oct 31 (~3 days), same process, never restarted.
I did not deliberately induce system-wide memory pressure: the machine is in active use and that test would page unrelated apps. I can run it in a controlled window if it changes the diagnosis.
3. Latest release
Still happens on v2.2.5: one execute call leaves RSS +257 MiB above baseline, nothing is reclaimed over 16 idle minutes, and RSS keeps stepping up at idle from the catalog refresh.
What this changes about the close reason
"Couldn't tell real retention on macOS apart from the allocator keeping freed pages" can now be split: the headline RSS number is mostly mimalloc holding freed pages resident (clean pages, instantly reclaimable by the OS). The genuinely retained part is smaller but real and permanent — phys footprint 75 → 190 MiB after one churn turn, still 190 MiB after 16 idle minutes — and the field process holds 520 MiB footprint after days. Operationally, RSS is what Activity Monitor, ps, and container limits show users, and on Linux the same workload returns to baseline within minutes (per the v2.0.16 Linux test above), so the asymmetry is still worth a look: either mimalloc should decommit on macOS the way it does on Linux, or the catalog-refresh and Code-Mode churn should stop growing it.
@opencodejr The data you asked for is in the comment above (allocator numbers next to RSS, a 16-minute idle timeline, and a repro on v2.2.5). Could you take a look and reopen when you get a chance? I can also run the memory-pressure window if that would change the diagnosis.
Activity
ChangeHow commentedon Sep 25, 2026
TL;DR
Long-running
opencode serve(v2.0.16, macOS arm64) shows usage-driven memory retention that never gets returned: fresh serves start at ~161-166 MB RSS, idle instances stay flat for days (~300 MB after 4.5 days), but active use raises RSS in permanent steps - a heavily-used instance climbed 161 MB -> ~1.0 GB in 7 h, and a synthetic Code Mode test (3 rounds x ~76 MB allocations in oneexecute) spiked the serve process by +732 MB (~3.2x amplification) with zero reclamation over 30 idle minutes. Deleting sessions via API doesn't help; recovery is restart-only.Code-level causes (verified at
34aa427):session/compaction.ts:309only stampstime.compacted;state.output/ tool content / attachments are never cleared (matches fix: clear tool output and attachments when pruning to prevent memory leak #7049, still unfixed).core/src/event.ts:175usesPubSub.unbounded()for the fan-out and all per-type queues; a single slow subscriber turns activity into permanent heap growth (plausible driver of server mode: long-running opencode serve accumulates anonymous JS heap/swap; 26.8GiB cgroup peak, WKFastMalloc/JSJITCode #33213's 26.8 GiB serve).codemode/interpreter/runtime.tsruns in-process with limits only for timeout/tool-calls/output-bytes, so heavy allocations land directly in the serve heap.Full details and measurements in the issue body above.
[-]serve: RSS grows in permanent steps with usage and is never returned (prune keeps tool payloads; unbounded event queues; in-process Code Mode without heap ceiling)[/-][+]serve: RSS grows with activity and remains elevated while idle[/+]github-actions commentedon Sep 25, 2026
Thanks for updating the issue with a clear description and reproduction steps!
renehonig commentedon Sep 29, 2026
Same signature here, but at larger scale and with heap-snapshot data: a SIGUSR1 snapshot at a 23 GB RSS plateau showed only 172 MB of live JS (134:1), with ~21.7 GB sitting committed-but-empty in the JSC allocator region — so the elevation looks like native allocator non-decommit (cf. oven-sh/bun#21560) fueled by per-turn history decode churn, not retained app state. We also identified the churn sources (per-turn full-history JSON decode, git snapshot storms, a 5-min models.dev re-emission wave with no content-digest skip). Full evidence in #52041 — happy to compare readings with your case to see if it's one issue or two.
VariableVince commentedon Oct 11, 2026
@ChangeHow Was this solved for you since v2.0.21? From #51761 it looks like it hopefully should be
ChangeHow commentedon Oct 13, 2026
let me test again.
ChangeHow commentedon Oct 13, 2026
Thanks for the pointer. I retested this on v2.0.23 (macOS arm64, isolated standalone
serve), and it looks like #51761 and this report are different issues.Retest
servebaseline: 164.0 MiB RSSexecutewith 3 rounds of ~76 MiB temporary array/string churn: 995.3 MiB (+831.3 MiB)204): 1003.7 MiB after another 10 secondsI also ran a cold-start control with no tool call:
serve: 162.7 MiBSo the Code Mode stress run ended roughly 549 MiB above the no-tool control, and there was no meaningful RSS reclamation over 30 idle minutes.
This is still only an RSS observation; it does not prove that the temporary JS objects remain reachable. The pages may have been freed internally but retained by the Bun/JSC allocator. Operationally, however, the
serveRSS high-water behavior reported here is still reproducible.#51761 was a runaway allocation/OOM in the TUI, and its associated fix #52233 updated OpenTUI to 0.5.13. That can fix the TUI issue without changing the
serve/Code Mode allocator behavior measured here.So unfortunately: no, this specific case is not solved for me on v2.0.23; it appears to be a separate issue.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed measurements. I tried to reproduce this on v2.0.16 but on Linux x64, since macOS isn't available here, and the memory came back down.
Setup: a simulated model had the server run Code Mode
executewith three rounds of large temporary array/string allocations per call (roughly 60–80 MB each). I sampled theserveprocess RSS withps -o rss=.On Linux, then, the memory from this workload is freed and returned to the OS. Nothing stays retained between calls. Your numbers come from macOS arm64, where the system allocator may keep freed pages counted in RSS. That would look like retention even if nothing on the JS heap is still reachable.
These would help narrow it down:
bun add -g @opencode/cli@latest).You can reply here with the details, or edit the issue and then leave a comment saying you've updated it.
opencodejr commentedon Oct 31, 2026
I'm closing this for now, since we haven't heard back. I couldn't reproduce the memory growth on Linux with v2.0.16. After the same kind of large Code Mode
executeallocations, theserveprocess RSS went back to its starting level within a minute or two of idling. We couldn't tell real retention on macOS apart from the allocator keeping freed pages, so we couldn't take it further.If it still happens for you, please comment here or open a new issue with:
We can reopen and look again then. Thanks for the detailed report!
ChangeHow commentedon Oct 31, 2026
Reopening with the data from the closing comment. Short version: it still happens on the latest release (v2.2.5), idle RSS does not come back, and the vmmap/footprint numbers now split "real retention" from "allocator keeps freed pages" — the RSS inflation is mostly the latter, with a smaller real-dirty part that also stays.
Setup
opencode-darwin-arm64.ziprelease binary, macOS 26.6.2, arm64opencode serve --hostname 127.0.0.1 --port 4455executecall with 3 rounds of ~76 MB temporary allocations (40 MB string + ~36 MB number array per round, released between rounds) — same shape as the original reportps -o rss=; footprint viafootprint(1); regions viavmmap -summary1. Allocator numbers next to RSS
Of the 623 MiB idle RSS, only ~190 MiB is private dirty (footprint). The mimalloc region alone holds 450 MiB resident with 136 MiB dirty, so ~314 MiB of RSS is freed-but-still-resident allocator pages. JSC itself stays tiny (Gigacage dirty ~5 MiB, JIT ~19 MiB). I could not pull an in-process JS heap snapshot from the compiled release binary (
Bun.generateHeapSnapshot()needs in-process access and serve exposes no such endpoint); #52041's SIGUSR1 snapshot (172 MB live JS at a 23 GB plateau) is the JSC-level datapoint and matches this shape.The same pattern in the field: the OpenChamber-managed serve on this machine (v2.2.0, up 3 days, mostly idle) sits at 1.70 GiB RSS but only 520 MiB phys footprint (peak 991 MiB), with Memory Tag 240 resident 1.4 GiB / dirty 394 MiB.
2. Longer idle
provider.updated/model.updatedevents at 10:23:09Z with no user activity — matching the "models.dev re-emission wave" described in serve: RSS ratchets to 25-32 GB under concurrent agent turns; heap snapshot shows only 172 MB live JS (allocator-side retention) #52041. Timeline (KiB): 420,816 flat for ~9 min → 638,016 → flat to the end.3. Latest release
Still happens on v2.2.5: one
executecall leaves RSS +257 MiB above baseline, nothing is reclaimed over 16 idle minutes, and RSS keeps stepping up at idle from the catalog refresh.What this changes about the close reason
"Couldn't tell real retention on macOS apart from the allocator keeping freed pages" can now be split: the headline RSS number is mostly mimalloc holding freed pages resident (clean pages, instantly reclaimable by the OS). The genuinely retained part is smaller but real and permanent — phys footprint 75 → 190 MiB after one churn turn, still 190 MiB after 16 idle minutes — and the field process holds 520 MiB footprint after days. Operationally, RSS is what Activity Monitor,
ps, and container limits show users, and on Linux the same workload returns to baseline within minutes (per the v2.0.16 Linux test above), so the asymmetry is still worth a look: either mimalloc should decommit on macOS the way it does on Linux, or the catalog-refresh and Code-Mode churn should stop growing it.ChangeHow commentedon Oct 31, 2026
@opencodejr The data you asked for is in the comment above (allocator numbers next to RSS, a 16-minute idle timeline, and a repro on v2.2.5). Could you take a look and reopen when you get a chance? I can also run the memory-pressure window if that would change the diagnosis.