Skip to content

Standalone executables leak a fresh TMPDIR copy of every dlopen'd embedded native library on each launch #40076

Closed as not planned

Description

@Archipel

What version of Bun is running?

1.3.14

Which platform(s) does your issue occur on?

Linux x64 (WSL2, kernel 6.6). Likely affects every platform where bun build --compile output dlopens embedded native libraries.

What build steps are needed to reproduce?

$ cc -shared -fPIC -o lib.so lib.c   # int marker(void) { return 42; }

entry.ts:

import lib from "./lib.so" with { type: "file" }

const { dlopen } = await import("bun:ffi")
dlopen(lib, { marker: { args: [], returns: "int" } })
console.log("dlopened:", lib)

Build and run the standalone executable twice:

$ bun build --compile --target=bun-linux-x64 ./entry.ts --outfile app
$ ./app && ./app

What is the expected behavior?

The temporary extraction of an embedded native library should be cleaned up after dlopen (or reused across launches), so running a compiled binary does not grow $TMPDIR.

What do you see instead?

Every launch extracts a fresh copy of the embedded library to $TMPDIR/.<hash>-<index>.so and never deletes it — not even on clean exit. After the two runs above:

$ ls -la /tmp/.59ee* 
-rw-r--r-- 1 thomas thomas 15112 Aug 22 11:20 /tmp/.59ee776dfc9ebfd7-00000000.so
-rw-r--r-- 1 thomas thomas 15112 Aug 22 11:20 /tmp/.59ee776dfddbfeff-00000000.so
-rw-r--r-- 1 thomas thomas 15112 Aug 22 11:16 /tmp/.59ee77edf4bfbfbf-00000000.so
-rw-r--r-- 1 thomas thomas 15112 Aug 22 11:16 /tmp/.59ee77edf7b2bf97-00000000.so

One leaked file per process launch (the two 11:16 files are from earlier runs of the same binary).

Real-world impact

opencode ships a ~14 MB libopentui.so inside its compiled binary; it gets extracted to a fresh temp file on every launch and never unlinked. A user accumulated 44 orphaned copies (~533 MB), which filled /tmp completely — after which opencode failed to start at all with a misleading "Failed to open library ... No such file or directory" (the extraction write hits ENOSPC, so the path doesn't exist by the time dlopen runs).

Notes for a fix

  • Unlinking the temp file right after dlopen returns is safe on Linux/macOS: the inode survives for the existing mapping, and typical consumers (e.g. OpenTUI) never re-open the path. On Windows the unlink of a loaded DLL will fail, which can be ignored or deferred.
  • Alternatively, reuse one cached extraction per embedded library across launches (keyed by content hash) instead of extracting per launch.
  • The current failure mode when $TMPDIR is full also produces a confusing error: the reported bunfs path (/$bunfs/root/lib...so) looks like a packaging bug rather than ENOSPC.

Activity

  1. robobun commented on Aug 22, 2026

    @robobun
    Collaborator

    This is fixed in Bun 1.4.0 by #29587. Each embedded library now extracts to one content-hashed file, $TMPDIR/.bun-{uid}-{contenthash}.{ext}. Every launch reuses that file instead of creating a new one.

    Verified with your repro on 1.4.0: four launches of the compiled binary leave one file in $TMPDIR, not four. On 1.3.14 the same steps leave four.

    Upgrade to Bun 1.4.0 or later. The opencode case stops accumulating copies once it ships with a fixed Bun. Existing orphaned .{hash}-{index}.so files from 1.3.x are safe to delete.

  2. Archipel commented on Aug 22, 2026

    @Archipel
    Author

    Duplicate of #29585, fixed by #29587 which shipped in Bun v1.4.0.

    For the record, this repro confirms the bug on v1.3.14 (which predates the fix) — worth noting since binaries built with 1.3.x keep leaking even after users upgrade Bun itself, until the app ships a rebuild.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions