Skip to content

CLI leaks ~10 MB of RAM per launch: bundled libopentui.so is unpacked to a temp file and never deleted (Termux/Android) #1443

Description

@heavymio

What happened

Every launch unpacks the bundled libopentui.so into a temporary file and dlopens it, but never deletes the file. One file per launch, ~10 MB each, and the name is unique per launch so nothing is ever reused.

$ ls -la /tmp/.*.so
-rw-r--r-- 1 root root 10240680 Sep 27 08:59 /tmp/.1adb7be55fff07df-00000000.so
-rw-r--r-- 1 root root 10240680 Sep 27 09:00 /tmp/.1adb7bed3b9d5ee7-00000000.so
-rw-r--r-- 1 root root 10240680 Sep 27 09:05 /tmp/.1adb7abd59df37fb-00000000.so

Details that make this easy to confirm:

  • size is always exactly 10240680 bytes, and the md5 is identical across launches (f29cb944…) — the same library, over and over
  • the file is a valid ELF64 AArch64 shared object with SONAME: libopentui.so
  • the blob is embedded in the shipped binary: a 64-byte chunk from the middle of the extracted file is present in freebuff
  • exactly one file per launch, on every exit path — --version, --help, Ctrl-C, through the npm launcher, and direct execution all leak one

It leaks regardless of how the process exits, so this is not an artefact of killing it.

On Android /tmp is a tmpfs, so this is RAM, not disk. Measured over one working session: 35 launches → 35 files → 342 MB resident. At a few dozen launches a day that is roughly 300 MB/day and ~9 GB/month that nothing ever reclaims.

I have not found a way to make Bun reuse the file. Bun does have a built-in extraction path — when a compiled binary dlopens a library imported with with { type: 'file' }, Bun unpacks it to /tmp/.bun-<hash>.so — but that one is deduplicated by name, so repeated runs reuse the same file. Stock Bun 1.2.21 and 1.4.2 both do this and do not grow. The per-launch unique name here is the CLI's own extraction, so the unlink is missing on that path.

Steps to reproduce

rm -f /tmp/.*.so
freebuff --version
ls -la /tmp/.*.so        # one 10240680-byte file
freebuff --version
ls -la /tmp/.*.so        # two

Any invocation works; --version is used here only because it exits on its own. Repeat N times and you get N files.

Safe to delete while running, which makes it easy to confirm nothing depends on the file after startup:

freebuff & sleep 8; rm -f /tmp/.*.so; sleep 5; kill -0 $! && echo "still running"

That keeps the process alive with no errors, which is what makes the fix below safe.

Workaround, if a fix is not immediately available — point TMPDIR somewhere disposable and sweep it:

mkdir -p /path/to/scratch
TMPDIR=/path/to/scratch freebuff

Where does this happen?

CLI (terminal client)

Operating system

Linux

Version

0.0.204 (linux-arm64)

Model

Pixel 8 Pro (Tensor G3), aarch64, Android 16, Termux with proot-distro 5.9.0. Also reproduces on the direct binary at ~/.config/manicode/freebuff, so it is not a launcher issue.

Logs or screenshots

$ for i in 1 2 3; do freebuff --version; done
0.0.204
0.0.204
0.0.204
$ ls /tmp/.*.so | wc -l
3
$ du -ch /tmp/.*.so | tail -1
3 total
30M     <- ~10 MB per launch

readelf -d on the extracted file:

SONAME: Library soname: [libopentui.so]
NEEDED: libm.so.6, libpthread.so.0, libc.so.6, libdl.so.2

Build path baked into the library, for reference:

/Users/runner/work/_temp/.../zig-aarch64-macos-0.15.2/lib

Suggested fix

Delete the file as soon as dlopen returns — the library is mapped into the process at that point and the file is no longer read. Verified on this machine: removing it 8s into a running session leaves the process running with no errors.

import { dlopen } from 'bun:ffi'
import { unlinkSync } from 'node:fs'

const handle = dlopen(path, { symbols })
try {
  unlinkSync(path)   // mapped already; the file is no longer needed
} catch {
  // couldn't remove it — not worth failing startup over
}

If the library is reached through Bun's own embedded-file path (/$bunfs/...), no extra temp file is needed at all — unlinking the file Bun already unpacked is the whole fix.

Unrelated to #1374, but same runtime: os.cpus()[0].model throws when /proc/stat and /proc/cpuinfo disagree, which on this machine is an upstream Bun issue — oven-sh/bun#44125

Activity

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

    area:cliThe Codebuff/Freebuff terminal clientbot:triagedClassified by the community triage bottype:bugA defect in the code with a reproducible failure

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions