Repository navigation
Conversation
build_with_vm writes the client files, the server files under --debug-dump-server-files and the runtime chunk to dist/. Each of the three call sites printed 'Failed to write ... to output directory' and continued, so the build prerendered the pages, printed 'done' and exited 0. The three sites now go through one OutputDir, which reports each failed write as before and counts it. After the last write and before the prerender, the build exits 1 when the count is not zero. Every failed write is still reported in one run. This is the first version of #39295, ported to current main.
|
Status: ready for review. The diff is green on every lane that ran its tests.
How I reproduced it: four files with no dependencies (the PR body has them), and a regular file at PR: #47194 |
|
Updated 2:53 PM PT - Oct 28th, 2026
❌ @robobun, your commit e870296 has 6 failures in
🧪 To try this PR locally: bunx bun-pr 47194That installs a local version of the PR into your bun-47194 --bun |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Track production output write failures src/runtime/bake/production/mod.rs |
OutputDir tracks failed writes and whether it has attempted the runtime chunk. Client outputs, debug-dumped server outputs, and the runtime output use its writer. |
Stop builds after failed writes src/runtime/bake/production/mod.rs, test/bake/dev/production.test.ts |
The build stops when output writes fail. Tests check reported paths, exit codes, runtime write deduplication, and whether prerendering produced dist/index.html. |
Suggested reviewers: alii, jarred-sumner, dylan-conway
Priority:
Merge Risk: 
Minimal · up to e8702
-
Failed tracked output writes stop the build before prerendering. No merge-blocking issue was established.
Pre-merge checks: 
4
-
Full details: Description check
-
Explanation
The description explains the problem, cause, fix, verification, scope, limitations, and test results. It does not use the template headings verbatim, but it provides the required information in equivalent sections.
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline findings, I also checked for write sites the new counter could miss: the three former write_to_disk arms are the only ones in build_with_vm and all now go through OutputDir::write, and write_sourcemap_to_disk only records an index into the source-map table despite its name, so it has no failure path to count. The gate sits before PerThread::init, so no prerender happens after a failed write.
Extended reasoning...
The change replaces three copy-pasted error arms in src/runtime/bake/production/mod.rs with an OutputDir helper that counts failed writes and adds a Global::crash() gate before prerendering, plus a new describe block in test/bake/dev/production.test.ts. It touches no security-sensitive surface. Two confirmed inline findings (the double runtime-chunk write making a complete dist exit 1 under some umasks, and crash() bypassing process.on("exit")/exitCode unlike the sibling build_command arm) already signal that a human should look.
The loop over the output files writes the runtime chunk when it is a client file, or a server file under --debug-dump-server-files. The step after the loop then wrote it again. A failed write was reported twice. When only the second open failed, the build failed with a complete dist/_bun. OutputDir::write now attempts the runtime chunk once.
There was a problem hiding this comment.
The follow-up push (e870296) addresses the runtime-chunk double-write from my earlier review, and I found no new bugs; a maintainer should still weigh in because the PR description explicitly asks whether the Global::crash() gate should stay until the returned-error exit model lands.
What was reviewed:
OutputDir::writededupe:bake_is_runtimeis debug-asserted to be set on exactly one chunk, so the singleruntime_chunk_attemptedflag correctly covers the Client-side loop write, the--debug-dump-server-fileswrite, and the post-loop write without skipping a first attempt.- Refactor vs the three removed arms:
handle_error_return_trace, theOutput::errmessage, and write order are unchanged; the?sites in the loop still returnErrbefore the new gate as before. - Tests: the two new "written once" cases assert the exact failed-write list via
toEqual, so they fail if the chunk is attempted twice; the--debugmatrix isskipIf-gated with a stated reason.
Extended reasoning...
The change touches src/runtime/bake/production/mod.rs (the static build path of bun build --app) and test/bake/dev/production.test.ts, replacing three copy-pasted write_to_disk error arms with one OutputDir::write that counts failures and aborts via Global::crash() before prerendering. It touches no security-sensitive surface. The second commit resolves the double-write of the runtime chunk I raised earlier, and the refactor is behavior-preserving on the success path. Deferral rather than approval is because the exit mechanism (in-place Global::crash() like the existing invalid_routes gate, versus returning a typed error so process.on("exit") and process.exitCode are honoured) is an open design question the author has put to maintainers, and the bake subsystem's direction is itself under rework per the description.
Related to #39295, #39488
Problem
bun build --appexits 0 when it cannot write a file underdist/_bun/. It printsENOTDIR: Failed to write "_bun/<hash>.js" to output directory, prerenders anyway and printsdone.write_to_diskcall sites inbuild_with_vm(src/runtime/bake/production/mod.rs:720,:731,:812) print the error and continue.Fix
OutputDir::writereplaces the three error arms. It reports a failed write and counts it. Before the prerender, the build exits 1 if the count is not zero.test/bake/dev/production.test.ts, blockan output file that cannot be written fails the build. 7 of 7 tests fail on main.Background
build_with_vmwrites the client files todist/_bun/, then prerenders each route intodist/.bun build --no-bundle. An exit per arm reports only the first.Global::crash(), like theinvalid_routesgate. Review on bake: return bun build --app failures instead of exiting; name the output directory it cannot open #39302 asked for a returned error. Main has noError::BakeBuildFailed.Downsides
Failed to writeand exited 0 now exits 1. This includes a rebuild over read-only chunks with the same content.{ pages }route still exits 0 when a page cannot be written (bake: fail the build when a component throws during static prerender #33216).Notes
Source and reach. A read of
build_with_vmfound this during the work on #47146, another exit status bug of the same command. No user reported it, and no issue quotes the message.--appneeds a canary or debug build, orBUN_FEATURE_FLAG_EXPERIMENTAL_BAKE=1. The symptom needs a path underdist/_bun/that cannot be written whiledist/can. Main already exits 1 in three related cases:distis a regular file,dist/is read-only, or a page of a static route cannot be written.History. #39295 fixed this on 2026-08-16. Its first revision is this change: the
OutputDirrecorder and aGlobal::crash()gate. Review on #39302 asked that bake return its failures: "we don't get to decide if the process should exit here". The second revision of #39295 then returnedError::BakeBuildFailed. It was closed into the consolidation PR #39488. That PR carries the same recorder with that return, and it is not on main. Since then #46021 addedif route_scan.invalid_routes > 0 { Global::crash(); }tobuild_with_vm. This PR uses that form because main has noBakeBuildFailed.Questions for a maintainer.
Error::BakeBuildFailedand abuild_commandarm in this PR. The in-place exit runs noprocess.on("exit")listener and ignoresprocess.exitCode, like theinvalid_routesgate. I checked with a listener andprocess.exitCode = 3in the config, and a regular file atdist/_bun. Main runs no listener and exits 0. This build runs no listener and exits 1. A failed page write exits 3 and runs the listener on both.build_with_vmstay in that design? If it does not, close this PR and keep its test cases for the new build.Repro. Four files, no dependencies:
Main 11c3671 (a release build, and 1.4.4-canary.1+11c367163) writes
dist/index.htmland exits 0. With this PR: the sameFailed to writeline, nodone, nodist/index.html, exit 1.Not fixed here.
{ pages }routes. A page can reject: a render error, a null result, or an unwritable page. The build prints the error anddone, and exits 0.src/js/builtins/Bake.ts:173maps without a return. Lines:160and:167do not await a promise that is already rejected. bake: fail the build when a component throws during static prerender #33216 carries the fix and its test. Checked with a directory atdist/a/index.html:EISDIR,done, exit 0 on main and with this PR.bun build --appwhen two files match the same route pattern #47146.doneignoresprocess.exitCodeand runs noexitlistener (theOk(())arm ofbuild_command). bake: consolidate the open robobun dev-server, HMR runtime, production build and router fixes #39488 carries that change.--debug-dump-server-fileswrite is counted. Review on init: report failed scaffold writes, and do not create an entry stub that shadows a module #41741 says "if non-essential files fail to write and already existed we shouldn't exit 1". The dump is counted anyway because the flag asks for those files, and bake: consolidate the open robobun dev-server, HMR runtime, production build and router fixes #39488 counts it too. Say so if it should be report-only.The runtime chunk. Main can write it twice: the loop over the output files writes it, and the step after the loop writes it again. This happens always when only the client graph uses a runtime helper, because the chunk is then a client file. It happens with
--debug-dump-server-fileswhen the chunk is a server file. A failed write was then reported twice. With the gate alone, a build also failed when only the second open failed, with a completedist/_bun. I saw that as a non-root user with umask 0222.OutputDir::writenow skips the second attempt. In that umask case main printsEACCESand exits 0, and this build prints no error and exits 0. #46272 deletes the step after the loop. The skip is then dead code.Measurements. Two release builds (LTO) in one build directory: main 11c3671, and the same tree with this change.
strace,perf,valgrindandbloatyare not installed on my machine. The numbers come fromllvm-size -A,llvm-nm -Sandgdb..text108,646,665 to 108,646,409 bytes,.rodata+0.build_with_vm12,911 to 12,731 bytes.OutputDir::writeis a separate 109-byte function, and its drop glue is 40 bytes. The strippedbunis 138,197,144 bytes in both.distto the call toPerThread::init. Counted withgdbnextion the main thread, identical in 3 of 3 runs. One written file: 449 to 453 instructions inbuild_with_vm, plus 19 inOutputDir::write(+23). Two written files, one of them the runtime chunk: 584 to 586, plus 41 (+43).dist/is byte-identical, stdout isdone, exit 0.write_to_diskcalls 2 to 1 for 1 file, syscalls in the section 8 to 4. With--debug-dump-server-files: calls 5 to 4 for 4 files.dist/is byte-identical in both cases.dist/_bun: failure lines 2 to 1, and 5 to 4. No path is reported twice.dist/_bun, two outputs: both are attempted and reported (main: same). Exit 1 (main: 0). Main thread syscalls after the first failed write: 22 to 6. Pages written: 1 to 0.--debug-dump-server-filesdump (a regular file atdist/_bun/routes).EACCES).Global::crash()sites inproduction/mod.rs: 10 to 11.mod.rs+40/-26,production.test.ts+110/-3.Tests. All in
test/bake/dev/production.test.ts, blockan output file that cannot be written fails the build. Each test is onebun build --appchild process on a framework with no dependencies. Each asserts the exact list of reported files, exit code 1, empty stdout (nodone) and nodist/index.html.every failed write is reported: a regular file atdist/_bun. The stylesheet and the client entry point are both reported. No debug flag.a directory where a chunk goes: a directory at the path of the client chunk. The name comes from a control build inbeforeAll. The control also asserts that the fixture builds (done, exit 0).a runtime chunk in a client file is written once: only the client graph imports a CommonJS module. Main reports the chunk twice. No debug flag.a client file,the runtime chunk,a server file: one test for each of the three call sites.--debug-no-minifynames each output after its source, so a regular file where one directory goes blocks one output.a runtime chunk in the dump of the server files is written once: the same for--debug-dump-server-files. Main reports the chunk twice.--debugflags and skips the last four.toEqual, with exit code 0,doneand the page written. The twowritten oncetests also list the chunk twice there.--timeout 240000. The React cases need more than the default 5 s on a debug build, on main too.dev-and-prod.test.ts12 pass,dev/ssg-pages-router.test.ts10 pass,app-options.test.ts13 pass.Self-review. 14 concerns raised, 13 addressed. One concern was rejected:
--debug-dump-server-fileswrite report-only. The flag asks for those files, and bake: consolidate the open robobun dev-server, HMR runtime, production build and router fixes #39488 counts that write too.The runtime chunk took two attempts. A first commit skipped the second write with a flag in
build_with_vm, for the--debug-dump-server-filescase only. I dropped it because it did not cover a runtime chunk in a client file. Review on this PR then showed the umask case above. The skip is now inOutputDir::writeand covers both cases.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/bake/dev/production.test.ts