SIGN IN SIGN UP
oven-sh / bun UNCLAIMED

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one

0 0 150 Rust

Fix crash when starting a compiled executable in a deleted cwd (#31496)

## What

Starting a `bun build --compile` standalone executable from a directory
that has been deleted crashes with a segfault instead of a clean error:

```
❯ claude --dangerously-skip-permissions --continue
Transpiler: init failed: error.ENOENT
...
panic(main thread): Segmentation fault at address 0x641
```

## Reproduction

```sh
echo 'console.log("hi")' > app.js
bun build --compile app.js --outfile app
mkdir /tmp/gone && cd /tmp/gone && rmdir /tmp/gone && exec ./app   # adjust path
```

A process can keep a deleted directory as its cwd until the last fd to
it closes, and `getcwd()` then returns `ENOENT`. (POSIX-only — Windows
refuses to remove a directory that is a process's cwd.)

## Cause

A standalone compiled binary bypasses `Arguments::parse` (no global
flags, no baked exec-argv), so `absolute_working_dir` stays unset and
the **first `getcwd` of the entire startup** happens inside
`Transpiler::init` → `FileSystem::init` (`src/resolver/lib.rs:316`).
When the cwd is gone that `getcwd` returns `ENOENT`.

The per-VM init hook `init_runtime_state` (`src/runtime/jsc_hooks.rs`)
had no error channel, so it **swallowed** the error, logged `Transpiler:
init failed`, and left `vm.transpiler` as the zeroed bytes the allocator
handed out. The next read of that field (`configure_defines` →
`run_env_loader` → `Transpiler::env_mut`) dereferenced a null pointer →
crash.

The plain `bun <file>` / `bun -e` / `bun run` paths don't hit this:
`Arguments::parse` calls `getcwd` first and surfaces the ENOENT cleanly,
masking the bug — so only compiled executables crash.

## Fix

Widen `init_runtime_state` to return `Result<RuntimeState,
bun_core::Error>`. On `Transpiler::init` failure it unwinds the per-VM
state it set up (the `RuntimeState` box, its thread-local slot, and the
AST stores — mirroring `deinit_runtime_state`) and returns the error,
which `VirtualMachine::init` propagates with `?`. The CLI then turns it
into a clean `ENOENT` message and exits 1 — the path the plain `bun` CLI
already used, and what the reference (`try Transpiler.init(...)`,
VirtualMachine.zig:1241) intended. This also resolves the in-tree
`TODO(b2): widen init_runtime_state return to Result`.

## Verification

New test in `test/bundler/bun-build-compile.test.ts` compiles a
standalone binary and runs it from a deleted cwd.

- **Before:** the binary crashes — panic through `boot_standalone` →
`Transpiler::env_mut`, terminates via SIGILL (exit 132).
- **After:** `stdout` empty, `stderr` contains `ENOENT`, exit code 1.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
R
robobun committed
677fe1cfadad831f708d3ff18ba780aaae323f0f
Parent: 5ba2f65
Committed by GitHub <noreply@github.com> on 5/29/2026, 2:27:37 AM