Skip to content

hunk show fails with "Failed to open library /$bunfs/root/libopentui-*.so" when the temp directory is not writable (e.g. --read-only containers) #1127

Description

@vicmunoz

What happened?

Summary

hunk crashes on startup when the directory Bun uses to extract the embedded
OpenTUI native library ($BUN_TMPDIR, else $TMPDIR, else /tmp) does not
exist or is not writable by the running user. The error message points at the
virtual $bunfs path and gives no hint about the actual cause, which made this
hard to diagnose.

Environment

  • hunk version: 0.22.0
  • OS: Ubuntu 26.04 (x86_64) container image, run via Docker Desktop on macOS
  • Container started with a read-only root filesystem (--read-only)
  • User inside container: non-root (uid 1000), $HOME writable at build time only

Steps to reproduce

Minimal, no container needed:

TMPDIR=/nonexistent hunk show

Realistic case:

  1. Install hunk in a Docker image (curl -fsSL https://hunk.dev/install.sh | sh).
  2. Run the container with --read-only and no writable /tmp (no --tmpfs).
  3. Inside a git repo, run hunk show.

Actual behavior

hunk: Failed to initialize OpenTUI render library: Failed to open library "/$bunfs/root/libopentui-1qjv5tb2.so": /$bunfs/root/libopentui-1qjv5tb2.so: cannot open shared object file: No such file or directory

Expected behavior

Either hunk works with a read-only filesystem, or it fails with a message that
names the real problem (temp directory not writable / not exec-mountable) and
how to fix it.

Root cause

hunk is a bun build --compile binary. libopentui.so lives inside the
executable's embedded filesystem (/$bunfs/root/...). At runtime Bun's
dlopen writes it out to <tmpdir>/.bun-<uid>-<content-hash>.so (mode 0600)
and then dlopens the on-disk copy. If that write fails, Bun silently falls back
to calling dlopen() on the literal /$bunfs/... path, which glibc cannot
resolve, producing the misleading error above.

Confirmed with strace on a minimal Bun 1.4.2 standalone binary embedding a
.so:

  • writable tmpdir: openat("/tmp/.bun-<uid>-<hash>.so", O_RDONLY) → works
  • TMPDIR=/nonexistent, or a tmpdir not writable by the current uid, or a
    read-only rootfs: identical "cannot open shared object file" error
  • if the tmpdir is mounted noexec the failure is different
    ("failed to map segment from shared object")

Once the cache file exists, Bun only stats and opens it read-only; no further
writes are needed.

Workarounds

  1. Mount a writable, exec-allowed tmpfs and point Bun at it:
    docker run --read-only --tmpfs /var/tmp/bun:rw,exec,mode=1777 -e BUN_TMPDIR=/var/tmp/bun ...
    (Docker's --tmpfs defaults to noexec, so exec must be explicit.)
  2. Pre-populate the cache at image build time by running hunk once as the
    same uid (needs a pty and a diff, e.g. under script + timeout), so the
    read-only rootfs at runtime is fine. This is what I ended up using.

Suggestions

  • Detect the $bunfs path in this error and print an actionable message:
    the temp directory must exist, be writable by the current user, and not be
    mounted noexec; mention BUN_TMPDIR/TMPDIR.
  • Consider probing the tmpdir (access(W_OK) or a test write) before
    initializing OpenTUI so the failure is explicit rather than a fallback.
  • Optionally offer a documented cache location (e.g. $XDG_CACHE_HOME/hunk)
    so users of read-only or hardened environments have a supported knob, and
    document this in the install/README section on containers.
  • This is arguably also a Bun issue (silent fallback instead of surfacing the
    write error).

Version

0.22.0

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions