Repository navigation
V1 1.18.31: every opencode <script>.js / plugin-hook subprocess leaks a fresh 13.7 MB libopentui.so into $TMPDIR (17 GB in <2 h) #49283
Description
Activity
github-actions commented
on Sep 16, 2026 on Sep 16, 2026 – with GitHub ActionsContributorMore actionsThis issue might be a duplicate of existing issues. Please check:
- tui: leaks ~21MB .so per launch into /tmp; fills tmpfs and breaks TUI startup #42700: Open issue tracking the same
libopentui.soleak into/tmpon Linux (fills tmpfs and breaks TUI startup) - [FEATURE]: Upgrade bundled Bun from 1.3.14 to 1.4.2 #44945: Open feature request to upgrade the bundled Bun from 1.3.14 to ≥1.4.x, which is the upstream fix you identified
Your report adds useful detail about the per-script-invocation / hook-subprocess amplification vector and the minimal repro, which may warrant keeping it open or linking it to #42700 for tracking.
- tui: leaks ~21MB .so per launch into /tmp; fills tmpfs and breaks TUI startup #42700: Open issue tracking the same
Adding a macOS (darwin-arm64) data point for the same root cause, plus a second cost that this leak causes on macOS.
Version: opencode-ai 1.18.25;
BUN_BE_BUN=1 opencode --version→1.3.14. The v1devbranchpackage.jsonstill has"packageManager": "bun@1.3.14".Same shape on macOS, and it isn't only libopentui. Each
opencode serve/attach/ TUI launch writes its embedded native libraries under a new random name and never removes the dylibs. For example, the fffc-lib:launch file inode 1 $TMPDIR/.b9dff6ede3bff3ba-00000000.dylib(4,822,976 B)477118107 2 $TMPDIR/.b9dff6edfefebfa2-00000000.dylib(same bytes)477118129 3 $TMPDIR/.b9dff6eff3fe37ca-00000000.dylib(same bytes)477118144 libopentui.dylib(3.3–3.8 MB) behaves the same way for the TUI. One long-running macOS host we use for testing had built up 22,984 of these files, 43.3 GB in total.Second cost, specific to macOS: slow first
dlopen. Because every launch writes a new inode, macOS runs its first-load Gatekeeper assessment on the library every time.dlopenblocks the main thread infcntlwhilesyspolicydevaluates the file. That's 0.2–0.8 s per library on an idle Mac. On a busy host it was 92–114 s, becausesyspolicydprocesses the assessments one at a time and the leaked files keep adding to that queue. Re-opening the same file takes 0.00 s. So on macOS the leak also slows down every cold start, not just the disk.BUN_TMPDIR/TMPDIRmove the files elsewhere, but the names still change on every launch.Ask: build the 1.18.x releases with Bun ≥ 1.4.0 (oven-sh/bun#29587: content-hash names, reused across launches), or backport that change. That fixes both the leak and the per-launch assessment cost. #39876 was closed as fixed in V2 only.
Repro (macOS)
for i in 1 2 3; do opencode serve --port 0 & P=$!; sleep 5; lsof -p $P | grep "$TMPDIR" | grep -E '\.(dylib|node)$'; kill $P; wait $P 2>/dev/null; done ls -A "$TMPDIR" | grep -cE '^\.[0-9a-f]{16}-[0-9]{8}\.dylib$' # grows on every launch
Confirming this on three V1 releases and adding scale/mapping data from a heavily-automated setup.
Versions: 1.18.31, 1.18.32, 1.18.34 (binary:
~/.opencode/bin/opencode, 178 MB,Bun v1.3.14 (Linux x64 baseline)). OS: Ubuntu 24.04.4, kernel 7.0.0-34-generic x86_64,/tmpon ext4 rootfs (937 GB disk — tmpfs isn't the only casualty).Same fingerprint, independently reproduced: every
opencode <script>spawn drops one new/tmp/.9a*-00000000.so, exactly 13,745,312 bytes, byte-identical across thousands of copies (md5 d08bd9a52225cacba076f60cca5a697e). Yoga + miniaudio symbols,ziglang/zig-bootstrapstrings.--versionleaks nothing (same control as yours).Scale observed: 11,000–12,000 orphaned files per incident = 145–158 GB per incident, three separate disk-full events (root fs at 98–100%, machine unusable). Sustained rate during hook-heavy automation: ~10 spawns/min ≈ ~140 MB/min ≈ ~200 GB/day. The trigger on my side matches yours exactly: a plugin adapter spawning hook scripts via
spawnSync(process.execPath, [hookPath])on every tool event, whereprocess.execPathis the opencode binary.mmap/orphan data: of ~12k files present at a time, only 3 are ever memory-mapped (one per long-lived session, found via
/proc/*/maps). The other ~99.97% are pure orphans from the moment of extraction — nothing ever reads them again.Why it bites hard here:
opencode <script>also can't actually execute the script (Failed to change directory to …— the binary treats the positional arg as a directory), so every spawn is pure loss: hook never runs, 13.7 MB still left behind.Local mitigations I'm running (may help others until the Bun ≥1.4.0 rebuild lands):
- Patch the plugin adapter to spawn hooks with a real
nodeinstead ofprocess.execPath(guarded by an env override, falls back toprocess.execPath) — stops the leak at the source. - systemd user timer every 5 min running a keep-list sweep: snapshot
/proc/[0-9]*/maps+/proc/*/fdfor/tmp/.9a*, delete everything not mapped/open and older than 60s. Safe with live sessions; worst case ~700 MB between sweeps.
Happy to test a Bun 1.4.0+ build if someone cuts one.
- Patch the plugin adapter to spawn hooks with a real
I believe this was a bug in bun itself, if you update to v2 it shouldn't be an issue (given we are on a newer bun version):
https://opencode.ai/v2/docs
Summary
On Linux x64 with opencode 1.18.31 (stable, Homebrew), every invocation of the compiled CLI that executes a JS script — e.g. plugin hook subprocesses spawned as
opencode /path/hook.js— extracts a fresh 13,745,312-bytelibopentui.sointo$TMPDIR(/tmp) and never reuses or removes it. The embedded Bun runtime is v1.3.14, which predates the extraction-dedup fix (oven-sh/bun#29587, shipped in Bun 1.4.0).In an agent setup where a plugin adapter spawns guard hooks with
spawnSync(process.execPath, [hookPath])on every tool/message event, this leaks ~13.7 MB per event. Observed accumulation: 1,332 files / 18,308,755,584 bytes (~17.05 GiB) in under 2 hours on a 32 GiB tmpfs/tmp, still growing (~12 files / 20 s during active tool use).This is related to but distinct from #42700 / #28089 / #42880: this report pins the per-script-invocation / hook-subprocess amplification on current stable V1 and gives a deterministic, seconds-long repro that does not require a full TUI launch or a long-running server session.
Environment
/home/linuxbrew/.linuxbrew/Cellar/opencode/1.18.31/bin/opencode, 185,030,784 bytes, sha256f9dab32248695e9e…, built 2026-09-14Bun v1.3.14 (0d9b296a)(confirmed via strings in the binary)libopentui-h3hyjpa5.so/tmpis tmpfs (32 GiB), uid/gid 1000@openchamber/web1.23.2) →opencode serve(PID 1076) → per-eventopencode ~/.config/opencode/hooks/*.jschildrenTMPDIR/BUN_TMPDIRunset for the serverSteps to reproduce (minimal, no TUI, no server)
Result (verified):
Each file:
sha256 ce73133a58d35e35610ef53353ddeeeb93fb29505dde0cf1854ce25facee241d,SONAME libopentui.so(x86-64 ELF, not stripped, debug info, Zig/clang 20.1.2 via zig-bootstrap).Control:
opencode --versionwith the sameTMPDIR/BUN_TMPDIRleaves 0 files, so this is specific to the script/hook execution path, not all CLI commands.Observed in normal use (hook amplification)
spawnSync(process.execPath, [hookPath])(~/.config/opencode/plugins/gsd-core.js:230)./proc/<serve-pid>/task/<tid>/children+/proc/<pid>/mapsfor 20 s caught 23 short-livedopencode .../hooks/*.jschildren, each mapping a distinct, freshly-created 13,745,312-byte/tmp/.*-00000000.so.lsofon older copies shows none held open; files persist after the creating processes exit.Expected behavior
Each process reuses a single extracted copy (e.g. a content-hashed path) or cleans up after itself.
$TMPDIRmust not grow without bound from process invocations.Actual behavior
One new randomly-named 13.7 MB copy per
opencode <script>.jsinvocation; no dedup, no unlink on clean exit.Impact
/tmpon tmpfs consumes RAM: ~17 GiB already; ENOSPC is reachable within hours of agent use.Failed to open library "/$bunfs/root/libopentui-*.so"error (see tui: leaks ~21MB .so per launch into /tmp; fills tmpfs and breaks TUI startup #42700).opencode <script>(plugin hooks, agent orchestrators, watchers) leaks proportionally to its event rate.Root cause / attribution
bun:ffidlopen()of embedded native libs in compiled binaries names each copy viatmpname(nanoTimestamp + per-VM counter)and never reuses/unlinks it. Fixed by Dedupe extracted embedded native modules in compiled binaries oven-sh/bun#29587 (content-hashed.bun-{uid}-{hash}.{ext}reuse, shipped in Bun 1.4.0); tracked in Compiled binary: Workers leak extracted native .so/.dylib files in /tmp (never cleaned up) oven-sh/bun#29585, fix(ui): avoid blocking on large file diffs #30962, fix(core): expand AWS_REGION in Bedrock Mantle base URL #40076.packageManagerin the rootpackage.json). The upgrade is already tracked: [FEATURE]: Upgrade bundled Bun from 1.3.14 to 1.4.2 #44945 (open) and PR chore: bump embedded Bun to 1.4.2 #44946 (open). This report is direct evidence that the V1 pin is the remaining source of the leak on stable.@opentui/coreeagerly resolves anddlopen()s its embeddedtype: "file"native lib at module init, so merely importing the TUI code materializes the library — in a--compilebinary that materialization goes through the buggy Bun path above.Workaround
.bcddef7ff5a7b7ee-00000000.so(a different embedded lib, held open byopencode serve) is not touched:find /tmp -maxdepth 1 -type f -regextype posix-extended \ -regex '.*/\.[0-9a-f]{16}-00000000\.so' -size 13745312c -mmin +60 -deleteRelated issues
opencode run --format jsonextracts libopentui.so to /tmp on every invocation, causing unbounded disk growth #21427, Headless opencode serve still extracts libopentui.so into /tmp on every start, causing temp artifact buildup #20043, Runaway /tmp .589*-00000000.so artifacts can exhaust disk during long-running usage #16996, opencode serve leaks ~14GB/hour of .so files in /tmp due to non-pooled ripgrep Workers #23804, v2 cli: headless commands load OpenTUI and leak native temp files #37671