Repository navigation
Conversation
echo and which cast SystemError.errno to the exit code. That field holds the negated errno, so ENOSPC (28) came out as 65508. export and cd ignored the write error and exited 1. All four now read the errno through SystemError::get_errno(), like cat and rm. mv reads the errno of a failed rename through bun_sys::Error::get_errno() for the same reason, which does not change its value on POSIX.
|
Reproduced on bun 1.4.1 with With this branch all four exit 28. Test: CI: the shell tests pass on every lane. The two red jobs in the latest run are unrelated to this change and are reported separately: Superseded by #32278, which fixes the same sites with exit 1 instead of the positive errno. See the closing comment below for the reasoning. |
|
Warning Review limit reached
On-demand reviews are free for the next 24 days. After that, they cost $0.25 per reviewed file. Or wait 3 minutes for your next included review. View limit detailsLimit details: You’ve used all 5 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (6)
Comment |
|
Updated 11:29 PM PT - Aug 27th, 2026
❌ @robobun, your commit 8cef172 has 2 failures in
🧪 To try this PR locally: bunx bun-pr 40702That installs a local version of the PR into your bun-40702 --bun |
|
On the review note about |
There was a problem hiding this comment.
LGTM — thanks for adding the exclusion rationale for pwd/basename/dirname/seq/yes; restoring only the four that regressed in the Zig→Rust port is a coherent scope.
Checked that SystemError::get_errno() and bun_sys::Error::get_errno() both return the positive E discriminant (Windows libuv codes canonicalized), so the as ExitCode cast is well-defined everywhere it's used. The cd fallback of 1 when the stderr write succeeds preserves the prior "chdir failed" contract. Test is Linux-gated for /dev/full and asserts against the pre-fix 65508/1 values, so it fails on stock bun.
Extended reasoning...
Overview
Five one-line changes in shell builtins (cd, echo, export, mv, which) swap raw e.errno field reads or hardcoded 1 for e.get_errno() as ExitCode in the write-failure branch of on_io_writer_chunk, matching what cat/rm already do and what the pre-rewrite Zig implementation did. One Linux-only test in bunshell.test.ts redirects each to /dev/full and asserts all exit 28 (ENOSPC).
Security risks
None. This only changes the numeric exit code reported after a write to stdout/stderr fails; no new input parsing, no allocation, no privilege or filesystem operations.
Level of scrutiny
Low. The change is mechanical and follows the established cat.rs/rm.rs pattern. I verified both get_errno() implementations (src/sys/Error.rs:165 for bun_sys::Error, src/sys/lib.rs:53 for SystemError) return the positive E discriminant with a checked constructor, so the as ExitCode cast cannot wrap negative on any platform. The mv.rs site operates on bun_sys::Error (already-positive errno), so behavior is unchanged there — it's a consistency edit.
Other factors
My earlier inline comment asked why five sibling builtins with the same hardcoded-1 shape were excluded. The PR description now names all five and explains they exited 1 in the original Zig too — this PR only restores the four that regressed during the port, deferring the broader "one status for every builtin" question to the referenced open PR. That satisfies REVIEW.md's "if a site is intentionally excluded, say so in the PR." No code changed since my prior review (the second commit is an empty CI retrigger). No CODEOWNERS cover these paths. The test uses a single .toEqual on an object per test/CLAUDE.md and would report {echo: 65508, which: 65508, export: 1, cd: 1} on the unfixed build.
|
Closing in favor of #32278, which fixes the same exit code cast in Why exit 1 and not the positive errno:
The |
Problem
echo hi > /dev/fullandwhich sh > /dev/fullexit 65508 (ShellError: Failed with exit code 65508).export > /dev/fullandcd /nonexistent 2> /dev/fullexit 1 and drop the errno.echo.rs:116andwhich.rs:182castSystemError.errnoto theu16exit code. That field holds the negated errno (fill_system_error_common,src/sys/Error.rs:371), so ENOSPC (-28) wraps to 65508.export.rs:85andcd.rs:127hard-code 1.Fix
SystemError::get_errno(), which undoes the negation.catandrmalready do this, and the Zig builtins did too (getErrno()).cdkeeps exit 1 when the stderr message is written without error.mvreads a failed rename's errno throughbun_sys::Error::get_errno()instead of the raw field. That field is already positive, so the value does not change on POSIX.pwd,basename,dirname,seqandyeskeep exit 1 on a failed write. They did that before the rewrite too. shell: exit 1 and report a failed output write in every builtin #40033 proposes one status for every builtin.test/js/bun/shell/bunshell.test.ts(new test, stock bun reports 65508/65508/1/1). Also the rest ofbunshell.test.ts,commands/{echo,which,mv}.test.ts,epipe.test.ts,shell-pipe-read-fault.test.ts,shell-write-fault.test.ts.Background
IOWriter, the shell's per-fd write queue. The result arrives later in the builtin'son_io_writer_chunkas anOption<bun_sys::SystemError>.bun_sys::SystemErroris the JS-facing error shape. Itserrnois stored negated to match Node'serr.errno.get_errno()returns the positiveEvalue.bun_sys::Erroris the syscall-level error. Itserrnois the positiveu16.Notes
Values from the repro with
/dev/fullas the redirect target (Linux, ENOSPC = 28):echo hi > /dev/fullwhich sh > /dev/fullexport FOO=bar; export > /dev/fullcd /nonexistent 2> /dev/fullcd /nonexistent(stderr ok)mv nosuch dstpwd > /dev/fullls / > /dev/fullOn Windows
SystemError.errnois the libuv code (UV_ENOSPC = -4075), so the raw cast gave a different garbage value there.get_errno()canonicalizes it to theEdiscriminant, so the exit code is 28 on every platform.The test is Linux only because
/dev/fulldoes not exist on macOS and Windows.The excluded sites, with the pre-rewrite Zig behavior (commit
23427dbc12^,src/shell/builtin/*.zig):pwd.zig,basename.zig,dirname.zig,seq.zig,yes.zig:onIOWriterChunkreturneddone(1)on a write error. The Rust port (pwd.rs:72,basename.rs:62,dirname.rs:64,seq.rs:209,yes.rs:166) does the same.ls.zig:onIOWriterChunkdropped the error.ls.rs:182does the same, sols / > /dev/fullexits 0.echo.zig,which.zig,export.zig,cd.zig:onIOWriterChunkreturneddone(e.getErrno()). The Rust port changed these four, and this PR restores them.#40033 and #32278 are open PRs that change the same sites to exit 1 (coreutils style) for every failed write. This PR keeps the errno as the exit code, which is what the other builtins report and what the Zig implementation did.
no test proof · iteration 1 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/shell/bunshell.test.ts