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
Per-turn Workers leak an event loop and ~0.8 MB each on Bun < 1.4 (OpenCode V1 up to 1.19.3) #776
Magic Context 0.49.4, OpenCode 1.19.1 (V1) built with Bun 1.3.14 (upstream pins bun@1.3.14 through 1.19.3), Linux x64 (WSL2)
What
Every short-lived Worker Magic Context starts and terminates leaks its event loop on Bun 1.3.14: 3 file descriptors ([eventpoll], [eventfd], [timerfd]) plus about 0.7–0.85 MB of native memory. GC never frees either. We measured about four per turn. The Workers the bundle creates per job are auto-search, the message worker, compaction-marker candidates and session warmup.
Measured
15 long-running opencode serve processes (0.2–31 h old) held 26,404 leaked loops. Across processes, native memory (unnamed anonymous mappings) grew 0.65–0.88 MB per leaked loop. Example: one 31 h server had 8,113 leaked loops and 5.3 GB of native memory, out of 7.1 GB total RSS + swap.
Controlled test on one server: 15 one-line turns with no tools gave +61 leaked loops and +52 MB of native memory, measured after Bun.gc(true).
The same 15 turns on the same OpenCode code rebuilt with Bun 1.4.2: 0 leaked loops, +15 MB.
Count per process: ls -l /proc/<pid>/fd | grep -c eventpoll.
Repro (Bun only)
// worker.js: self.onmessage = (e) => self.postMessage(e.data)import{readdirSync,readlinkSync}from"fs"constloops=()=>readdirSync("/proc/self/fd").filter((f)=>{try{returnreadlinkSync(`/proc/self/fd/${f}`).includes("eventpoll")}catch{returnfalse}}).lengthfor(leti=0;i<200;i++){constw=newWorker(newURL("./worker.js",import.meta.url).href)awaitnewPromise((r)=>{w.onmessage=r;w.postMessage(i)})w.terminate()}console.log("eventpoll fds:",loops())// Bun 1.3.14: 201, Bun 1.4.2: 1
Suggestion
Keep these Workers alive and reuse them instead of creating one per job. Bun's own advice is not to terminate Workers (oven-sh/bun#15964). Alternatively, document that OpenCode builds on Bun < 1.4 (V1 up to 1.19.3) leak memory on every turn. OpenCode 1.19.4 pins bun@1.4.4; we have not tested that release.
Thanks @moynzzz, this is a clear report, and the Bun-only repro with the 1.3.14 vs 1.4.2 comparison pins it down.
Until we change anything, the workaround is in your last line: OpenCode 1.19.4 and later ship with Bun 1.4.4, which no longer leaks a terminated Worker's event loop, so upgrading OpenCode should stop the growth. If you try it, the eventpoll count from your repro would confirm it.
Separately, you're on Magic Context 0.49.4, and the current release is 0.49.20. Its worker handling has changed since then (workers now close their own database handle before they're stopped), though they're still started per job, so it doesn't remove this leak on Bun 1.3.x.
We're in the middle of a larger internal change, so reusing these workers instead of starting one per job is queued rather than started. We'll post here when we pick it up.
Activity
magic-alfonso commentedon Oct 24, 2026
Thanks @moynzzz, this is a clear report, and the Bun-only repro with the 1.3.14 vs 1.4.2 comparison pins it down.
Until we change anything, the workaround is in your last line: OpenCode 1.19.4 and later ship with Bun 1.4.4, which no longer leaks a terminated Worker's event loop, so upgrading OpenCode should stop the growth. If you try it, the
eventpollcount from your repro would confirm it.Separately, you're on Magic Context 0.49.4, and the current release is 0.49.20. Its worker handling has changed since then (workers now close their own database handle before they're stopped), though they're still started per job, so it doesn't remove this leak on Bun 1.3.x.
We're in the middle of a larger internal change, so reusing these workers instead of starting one per job is queued rather than started. We'll post here when we pick it up.