Summary
When a Bun-compiled binary uses new Worker(), each Worker instance extracts embedded native modules (.so/.dylib files) to /tmp with unique hash-based filenames. These files are never cleaned up after the Worker is terminated, causing unbounded disk growth.
Reproduction
Setup
A compiled binary that:
- Embeds a native library loaded via
bun:ffi dlopen() (e.g., @opentui/core uses import("./libopentui.so", { with: { type: "file" } }) then dlopen())
- Creates Workers that trigger the native module loading path
Steps
# Build a compiled binary
bun build --compile --entrypoints ./index.ts --outfile ./myapp
# Run the binary - it spawns Workers for file operations
./myapp serve
Observed behavior
- Each
new Worker() call extracts the embedded native .so/.dylib to /tmp with a unique hash filename (e.g., .5ffbafXXX-00000000.so)
- When
worker.terminate() is called, the extracted file remains in /tmp
- Subsequent Worker creations extract new copies with different hashes instead of reusing existing ones
- Files are hidden (dot-prefixed), so
ls does not show them, but ls -a / find does
Measured impact (real-world: opencode serve)
- File size: ~4.3 MB each (Linux x64) / ~860 KB each (macOS arm64)
- Creation rate: ~9 files per 10 seconds when actively processing requests
- Leak rate: ~14 GB/hour on Linux
- Disk fill time: fills a 300 GB
/ partition in ~19 hours
Verification
# Monitor growth
watch -n 5 "find /tmp -maxdepth 1 -name '.*-00000000.so' -type f | wc -l"
# Identify the files
file /tmp/.5ffbaf*-00000000.so
# Output: ELF 64-bit LSB shared object, x86-64
nm -gU /tmp/.5ffbaf*-00000000.so | head -5
# Shows symbols from the embedded native library (e.g., opentui renderer)
Expected behavior
- Extracted native files should be reused across Worker instances (content-hash based deduplication)
- OR extracted files should be cleaned up when the Worker is terminated
- OR at minimum, a single extraction per unique module should be shared via the existing
$bunfs mechanism
Environment
- Bun: v1.x compiled binary mode (
bun build --compile)
- OS: Linux x64 (primary), also reproduced on macOS arm64
- Native loading:
bun:ffi dlopen() on a file imported with { with: { type: "file" } }
Workaround
Periodic cleanup via cron:
*/30 * * * * find /tmp -maxdepth 1 -name ".*-00000000.so" -mmin +5 -delete
Context
Discovered while running opencode in serve mode on a production server. The @opentui/core library embeds a platform-specific .so via bun:ffi, and opencode creates/destroys ripgrep Workers frequently for file search operations. Each Worker extraction leaks the native module to /tmp.
Summary
When a Bun-compiled binary uses
new Worker(), each Worker instance extracts embedded native modules (.so/.dylibfiles) to/tmpwith unique hash-based filenames. These files are never cleaned up after the Worker is terminated, causing unbounded disk growth.Reproduction
Setup
A compiled binary that:
bun:ffidlopen()(e.g.,@opentui/coreusesimport("./libopentui.so", { with: { type: "file" } })thendlopen())Steps
Observed behavior
new Worker()call extracts the embedded native.so/.dylibto/tmpwith a unique hash filename (e.g.,.5ffbafXXX-00000000.so)worker.terminate()is called, the extracted file remains in/tmplsdoes not show them, butls -a/finddoesMeasured impact (real-world: opencode serve)
/partition in ~19 hoursVerification
Expected behavior
$bunfsmechanismEnvironment
bun build --compile)bun:ffidlopen()on a file imported with{ with: { type: "file" } }Workaround
Periodic cleanup via cron:
Context
Discovered while running opencode in
servemode on a production server. The@opentui/corelibrary embeds a platform-specific.soviabun:ffi, andopencodecreates/destroys ripgrep Workers frequently for file search operations. Each Worker extraction leaks the native module to/tmp.