oven-sh/bun
Debugging
This page is the practical "what do I do when something is wrong" reference.
A test crashes
- Run with the debug build:
bun bd test <file>. - The crash handler prints a
bun.reportURL with both Zig and JS frames. Open it. - If you have a debugger attached, the address sanitiser flag may help:
bun bd --asan=on test <file>(slower, catches more). - To get exception-scope diagnostics from JSC:
BUN_JSC_validateExceptionChecks=1 BUN_JSC_dumpSimulatedThrows=1 bun bd ....
A test hangs
- Find which test hung:
bun bd test <file> --timeout=10000and look at the last "starting" log line. - Common culprits: an unawaited promise, a missing
port: 0, or a process started without ausingdeclaration. BUN_DEBUG_QUIET_LOGS=1 BUN_DEBUG_EVENTLOOP=1 bun bd ...shows event-loop tick boundaries.
A change builds locally but fails in CI
- Run
bun run ci:errors(orbun run ci:errors '#PR') for a rendered failure list. - Run
bun run ci:logsto save full job logs to./tmp/ci-<build>/. - Common platform differences:
- Windows: long paths, file locking,
\r\nline endings, Unicode codepage. Usebun.path.windowsandbun.sys.openatrather than POSIX-only paths. - macOS: code signing for FFI, FSEvents debounce.
- Linux: epoll vs kqueue, glibc vs musl on Alpine images.
- Windows: long paths, file locking,
Debug-log scopes
Set BUN_DEBUG_<name>=1 to enable a specific scope. Examples observed in source:
BUN_DEBUG_HTTP=1— HTTP client request lifecycle.BUN_DEBUG_FFI=1— FFI marshalling.BUN_DEBUG_INSTALL=1— Package manager.BUN_DEBUG_BUNDLER=1— Bundler steps.BUN_DEBUG_PARSER=1— Parser entry / exit.BUN_DEBUG_RESOLVER=1— Module resolution attempts.BUN_DEBUG_QUIET_LOGS=1— Disable everything else (useful when chaining).
The full list lives in Output.scoped(.<name>, .visible) declarations across the source. Grep for Output.scoped( in src/.
Sourcemaps
Bun builds with sourcemap markers always available. The crash handler uses them to print human-readable file:line for native frames. For JS frames in user code, sourcemap support is automatic when the code was bundled by Bun.
The inspector
bun --inspect <script> opens an inspector socket; chrome://inspect will pick it up. The protocol is JSC's standard one. The bundled bun-vscode extension (packages/bun-vscode/) attaches via the same protocol.
Profiling
bun bd --cpu-prof script.tsProduces a .cpuprofile file. Open in Chrome DevTools → Performance.
For a hot Zig path, build with Tracy support and use vendor/tracy/ (not on by default — see scripts/build/).
Memory leaks
Use Bun.gc(true) in tests to force a full GC, then check process.memoryUsage(). The gc helper is also exported from harness.ts.
For native leaks, mimalloc has built-in stats: MIMALLOC_VERBOSE=1 bun bd ....
A C++ assert fires
The error message includes file:line. Search the file. C++ asserts in JSC bindings (ASSERT(...) macros) usually mean a bug in the binding — wrong owner, wrong scope, missing exception check.
A Zig unreachable fires
This is a panic. The crash handler kicks in, gives you a bun.report URL with the offending function. Often the fix is to convert unreachable into an error or to explicitly handle the case.
A test passes locally but fails in CI
Order of likelihood:
- Flakiness. Look for
setTimeout, port reuse, ordering dependencies. - Platform. Re-read the failing job's OS. Add the platform-specific path with
if (process.platform === "win32"). - Missing CI env var. Some integration tests require credentials only set in CI — those should be
test.skipIfblocked locally. - Resource limits. CI runners have less RAM; tests that pre-allocate large buffers may OOM.
Reading PR feedback
gh pr view --comments silently omits review comments. Always use:
bun run pr:comments
bun run pr:comments 28838
bun run pr:comments --include-resolved
bun run pr:comments --json | jq '.[] | select(.user == "...")'This pulls all three GitHub comment endpoints and presents them in one chronological list.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.