Skip to content

bun_core: use strerror() texts in the macOS coreutils_error_map - #39264

Open
robobun wants to merge 3 commits into
mainfrom
farm/23b1245a/macos-coreutils-error-map
Open

bun_core: use strerror() texts in the macOS coreutils_error_map#39264
robobun wants to merge 3 commits into
mainfrom
farm/23b1245a/macos-coreutils-error-map

Conversation

@robobun

@robobun robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • On macOS, shell builtins and Output.err print made-up errno texts: mkdir existing reports mkdir: /path/existing: File or folder exists where bash, the BSD coreutils and Linux bun all say File exists; EBUSY prints Device / Resource busy, EPROCLIM quotas & mush. Too many processes, ESTALE Network File System. Stale NFS file handle, and so on. Linux and Windows are unaffected.
  • Cause: the macOS DELTA of coreutils_error_map in src/bun_core/result.rs:190 was transcribed from the comments in Apple's <sys/errno.h> (several with the header's section headings glued on) instead of from strerror(). 17 of Darwin's 106 errnos came out wrong: 16 DELTA rows, plus EOPNOTSUPP (a distinct errno, 102, on Darwin) which has no row and falls through to glibc's text.
  • The strings date from the original Zig table (d3a93d5, carried into the per-OS map by get node:fs tests passing part 1 #16270, whose comment says the table exists to match bash/coreutils). When the table was later split into BASE + per-OS DELTA, the bad rows survived as "overrides", and 7 of them override BASE rows that were already the correct Darwin text (EEXIST, EAGAIN, EDOM, ENOMEM, ENOTSOCK, ENETDOWN, ENOTSUP). An EWOULDBLOCK row names an errno Darwin's SystemErrno does not have, so it was unreachable.
  • Nothing tested these strings: every existing shell test pins a text that is identical on macOS and Linux, and the tables are #[cfg(target_os)]'d, so a test running on Linux never sees the macOS rows. Found while reviewing shell(mkdir): accept --verbose instead of the misspelling --vebose #39221, whose mkdir test accepts both spellings of EEXIST until this lands.

Fix

  • src/bun_core/result.rs: correct the 10 rows whose Darwin text really differs from glibc (EBUSY, EPROCLIM, ESTALE, EPWROFF, EDEVERR, EBADEXEC, EBADMACHO, EMULTIHOP, ENOLINK, ENOPOLICY), delete the 7 rows whose Darwin text is the BASE text and the dead EWOULDBLOCK row, add EOPNOTSUPP. 43 rows remain; all 106 Darwin errnos now resolve to Apple's errlst.c text. The FreeBSD DELTA was audited the same way and is already correct.
  • Why these texts: the map exists to print what bash/coreutils print, which is strerror(), and on macOS strerror() returns Apple Libc's sys_errlist. The new texts were checked against Go's syscall/zerrors_darwin_*.go, which is generated by calling strerror() on macOS (it lowercases the first letter, nothing else). EMULTIHOP (Reserved) and ENOLINK (Reserved) look odd but are literally what macOS strerror() returns.
  • Deleting the 7 redundant rows changes nothing visible beyond the text fix itself: with the corrected texts they would duplicate BASE, and the module's contract is that DELTA holds only divergent texts and OS-only errnos.
  • Tests:
    • test/internal/source-lints/coreutils-error-map.test.ts (new): parses BASE and both DELTAs out of result.rs and the three POSIX SystemErrno enums out of src/errno/, and checks, for macOS and for FreeBSD, that BASE + DELTA resolve every errno of the OS's enum to the OS's sys_errlist text and that the DELTA holds exactly the errnos whose text differs from BASE (so a missing, redundant, dead or re-worded row all fail). Apple's list is transcribed in full, in errno order; FreeBSD's is expressed as its eight differences from Apple's (both descend from 4.4BSD's errlst). A last check keeps BASE covering every Linux errno with no unreachable rows. Reading source is the only way to cover these rows from Linux; it runs in the source-lints workflow, which already triggers on src/**/*.rs. On main the two macOS checks fail with the 17 wrong rows and the 8 extra keys (output below) and the FreeBSD ones pass; with this change everything passes.
    • test/js/bun/shell/bunshell.test.ts: mkdir on an existing directory prints mkdir: <path>: File exists, the same text on every platform, so it needs no platform guard; it fails on macOS before this change.
    • src/errno/lib.rs: cargo test -p bun_errno asserts EEXIST's text everywhere and the macOS/FreeBSD/glibc EBUSY wording per platform (plus EOPNOTSUPP and EBADEXEC on macOS). Passes on Linux; cargo check --tests for aarch64-apple-darwin, x86_64-unknown-freebsd and x86_64-pc-windows-msvc compiles.
    • bun bd test test/js/bun/shell/bunshell.test.ts (whole file) and bun test test/internal/source-lints/ pass.

Background

  • coreutils_error_map (src/bun_core/result.rs) maps an errno to the short text GNU/BSD coreutils print for it, i.e. strerror()'s text. bun_sys::coreutils_error_map projects it onto the typed SystemErrno enum; shell builtins (Builtin::task_error_to_string, Error::to_shell_system_error) and Output.err format errors through it. Node-facing errors use the separate libuv table and are not involved here.
  • The map is stored as one BASE table holding glibc's texts (used as-is on Linux and Windows) plus a #[cfg(target_os)] DELTA for macOS and for FreeBSD; lookup consults DELTA first, then BASE. A DELTA row is therefore only meaningful when the OS's text differs from glibc's or the errno does not exist on Linux.
  • SystemErrno (src/errno/<os>_errno.rs) is the per-OS errno enum; the tables are keyed by its variant names, which is why a row naming a variant the OS lacks (the old EWOULDBLOCK row) can never be reached.
Lint output on main (before this change)
macOS: BASE + DELTA resolve every errno to the OS's strerror() text
  ENOMEM (12): "Out of memory", strerror() says "Cannot allocate memory"
  EBUSY (16): "Device / Resource busy", strerror() says "Resource busy"
  EEXIST (17): "File or folder exists", strerror() says "File exists"
  EDOM (33): "math software. Numerical argument out of domain", strerror() says "Numerical argument out of domain"
  EAGAIN (35): "non-blocking and interrupt i/o. Resource temporarily unavailable", strerror() says "Resource temporarily unavailable"
  ENOTSOCK (38): "ipc/network software - argument errors. Socket operation on non-socket", strerror() says "Socket operation on non-socket"
  ENETDOWN (50): "ipc/network software - operational errors Network is down", strerror() says "Network is down"
  EPROCLIM (67): "quotas & mush. Too many processes", strerror() says "Too many processes"
  ESTALE (70): "Network File System. Stale NFS file handle", strerror() says "Stale NFS file handle"
  EPWROFF (82): "Intelligent device errors. Device power is off", strerror() says "Device power is off"
  EDEVERR (83): "Device error, for example paper out", strerror() says "Device error"
  EBADEXEC (85): "Program loading errors. Bad executable", strerror() says "Bad executable (or shared library)"
  EBADMACHO (88): "Malformed Macho file", strerror() says "Malformed Mach-o file"
  EMULTIHOP (95): "Reserved", strerror() says "EMULTIHOP (Reserved)"
  ENOLINK (97): "Reserved", strerror() says "ENOLINK (Reserved)"
  EOPNOTSUPP (102): "Operation not supported", strerror() says "Operation not supported on socket"
  ENOPOLICY (103): "No such policy registered", strerror() says "Policy not found"

macOS: DELTA holds exactly the errnos whose text differs from BASE
  extra keys:   EAGAIN, EDOM, EEXIST, ENETDOWN, ENOMEM, ENOTSOCK, ENOTSUP, EWOULDBLOCK
  missing keys: EOPNOTSUPP

no test proof · iteration 0 · 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

The macOS rows of coreutils_error_map were transcribed from the comments
in Apple's <sys/errno.h>, several with the header's section headings glued
on, instead of from strerror(). Shell builtins and Output.err printed them:
on macOS `mkdir existing` reported "File or folder exists" where bash and
Linux bun say "File exists", EBUSY was "Device / Resource busy", EPROCLIM
"quotas & mush. Too many processes", and so on for 17 of Darwin's 106
errnos. EOPNOTSUPP (102 on Darwin) also fell through to glibc's text.

Correct the ten rows whose Darwin text differs from glibc, drop the seven
rows whose real Darwin text is the glibc text already in BASE, drop the
EWOULDBLOCK row (Darwin's SystemErrno has no such variant), and add
EOPNOTSUPP. Every Darwin errno now resolves to Apple's errlst.c text; the
FreeBSD table already did.

Add a source lint that checks BASE + the macOS DELTA against Apple's
sys_errlist and both DELTAs against the SystemErrno enums they are keyed
by (the tables are cfg'd per OS, so no runtime test on Linux can see the
macOS rows), a shell test for the mkdir EEXIST message, and per-platform
assertions in bun_errno's unit tests.
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 5 minutes

Limit details: You’ve used all 5 included reviews currently available under your plan.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 27c8cb05-df02-4f03-af69-eab53750eba7

📥 Commits

Reviewing files that changed from the base of the PR and between a42889a and 1b3e619.

📒 Files selected for processing (4)
  • src/bun_core/result.rs
  • src/errno/lib.rs
  • test/internal/source-lints/coreutils-error-map.test.ts
  • test/js/bun/shell/bunshell.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author

Status: CI is green on head 1b3e619 (Buildkite build 99190, 179/179 jobs; source lints, clippy and miri workflows pass). Ready for review.

Reproduced by auditing the macOS DELTA in src/bun_core/result.rs against Darwin's strerror() texts (Go's generated zerrors_darwin_*.go table as the reference): 17 of 106 Darwin errnos resolved to the wrong text, 7 more rows were redundant with BASE and one (EWOULDBLOCK) was unreachable. bun test test/internal/source-lints/coreutils-error-map.test.ts fails on main with that list and passes on this branch (the same checks now also pin the FreeBSD table, which was already correct); bun bd test test/js/bun/shell/bunshell.test.ts passes (the new mkdir EEXIST case fails on macOS without the fix); cargo test -p bun_errno passes and the darwin/freebsd/windows test arms cross-check.

The missing ENOEXEC row in the sibling libuv table (src/sys/libuv_error_map.rs, "ENOEXEC: unknown error" from spawn) turned up while reviewing this and is being handled separately.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — data-only correction of the macOS errno string table to match Apple's strerror(), with three layers of tests.

What was reviewed:

  • Verified each deleted DELTA row (EAGAIN, EDOM, EEXIST, ENETDOWN, ENOMEM, ENOTSOCK, ENOTSUP) falls through to a BASE text that already matches Darwin's sys_errlist, and that EWOULDBLOCK is absent from darwin_errno.rs (dead row).
  • Checked the new EOPNOTSUPP row: Darwin errno 102 is a distinct SystemErrno variant, so it needs its own text.
  • Confirmed the bunshell mkdir test relies on the file-level $.nothrow(), so the nonzero exit doesn't throw before the assertion; tempDir/join/$ are already imported.
  • The source-lint test's row regex matches the actual comptime_string_map! and SystemErrno enum syntax in the source files it parses.
Extended reasoning...

Overview

This PR corrects the #[cfg(target_os = "macos")] DELTA table in bun_core::coreutils_error_map (src/bun_core/result.rs), which maps errno values to the short text shell builtins print. The old table was transcribed from comments in Apple's <sys/errno.h> (with section headings glued on: "quotas & mush. Too many processes") rather than from strerror(). The fix corrects 10 texts, deletes 7 rows that duplicated BASE and one dead row, and adds EOPNOTSUPP. Three tests are added: a source-lint that parses the Rust tables and checks them against Apple's sys_errlist (so Linux CI covers the cfg-gated macOS rows), a per-platform Rust unit test, and an end-to-end mkdir test in bunshell.test.ts.

Security risks

None. This is a static string table used for user-facing error messages. No parsing of untrusted input, no auth/crypto, no memory management.

Level of scrutiny

Low-to-medium. The runtime change is pure data in a platform-gated block — no control flow, no logic. The strings are externally verifiable against Apple Libc's errlst.c and Go's generated zerrors_darwin_*.go. I spot-checked the deleted rows against BASE and confirmed each falls through to the correct Darwin text; I confirmed EWOULDBLOCK is not a Darwin SystemErrno variant and EOPNOTSUPP (102) is. The source-lint test is more elaborate but follows the established pattern in test/internal/source-lints/ (regex-parsing Rust source), and its regexes match the actual syntax of comptime_string_map! rows and the SystemErrno enum.

Other factors

  • No CODEOWNERS cover the touched files.
  • The bunshell test uses the file-level global $.nothrow() (set in beforeAll), so awaiting a failing mkdir returns {stdout, stderr, exitCode} rather than throwing — the assertion is reachable. All required imports (tempDir, join, $) already exist in the file.
  • The PR description states the mkdir test's expected text ("File exists") is identical on every platform; BASE holds "EEXIST" => "File exists" and Windows/Linux use BASE verbatim, so no platform guard is needed.
  • The FreeBSD DELTA was audited and left unchanged; the new lint checks it names real variants and diverges from BASE.
  • The PR is exceptionally well-documented, cites the upstream source of every text, and includes the failing lint output on main.

@robobun

robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 10:49 PM PT - Aug 15th, 2026

@robobun, your commit 1b3e619 is building: #99190

The FreeBSD check only looked at the rows present in its DELTA, so a
deleted or re-worded override (the bug class the macOS table had) passed.
Express FreeBSD's errlst as its eight differences from Darwin's and run the
same two checks over both BSD tables.
Comment thread src/bun_core/result.rs Outdated
Comment thread src/errno/lib.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — data-only correction of the macOS coreutils_error_map DELTA to match Apple's actual strerror() output.

What was reviewed

  • Each of the 10 corrected / 8 deleted / 1 added rows against the DARWIN_SYS_ERRLIST reference; the 7 deleted rows do fall through to identical BASE text, and the deleted EWOULDBLOCK row was unreachable (no such variant in darwin_errno.rs).
  • The new bunshell.test.ts mkdir case on Windows: ERROR_ALREADY_EXISTS maps to EEXIST in windows_errno.rs:708, BASE has EEXIST => "File exists", and err.path is set to the same absolute target the test asserts, so no platform guard is needed.
  • The source-lint's regexes against the actual shape of result.rs and darwin_errno.rs/freebsd_errno.rsSUCCESS = 0 is correctly excluded, and a row the row-regex fails to parse would surface as a mismatch in the completeness check rather than pass silently.
  • File-level $.nothrow() at bunshell.test.ts:36 means .quiet() without .nothrow() on the failing mkdir won't throw.
Extended reasoning...

Overview

This PR corrects the macOS DELTA of bun_core::coreutils_error_map in src/bun_core/result.rs — a static string→string lookup table that supplies the text shell builtins print for an errno. 10 rows are re-worded to Apple Libc's sys_errlist text, 7 rows whose corrected text equals BASE are deleted, the unreachable EWOULDBLOCK row is deleted, and EOPNOTSUPP (a distinct Darwin errno, 102) is added. A one-line comment is added above the block. src/errno/lib.rs gains a #[cfg]-branched Rust unit test. Two test files are added/extended: a new source-lint that parses BASE + both DELTAs and both BSD SystemErrno enums out of source and checks them exhaustively against the OS's sys_errlist, and a shell integration test that mkdir on an existing directory prints mkdir: <path>: File exists.

Security risks

None. The change is string-literal content in a static error-message table; no parsing, no allocation, no user input handling, no control flow.

Level of scrutiny

Low. The only runtime change is inside #[cfg(target_os = "macos")] and affects error-message wording only — the failure mode of a wrong row is a slightly-wrong message, not a crash or behavioral difference. The strings are sourced from Apple's errlst.c and cross-checked against Go's generated zerrors_darwin_*.go (which is produced by calling strerror() on macOS). The source-lint test transcribes Darwin's full 106-row sys_errlist and asserts BASE+DELTA resolve every one exactly, so any transcription error would fail there.

Other factors

  • I traced the new bunshell.test.ts case for Windows correctness: mkdir_non_recursiveSyscall::mkdir returns ERROR_ALREADY_EXISTS, mapped to SystemErrno::EEXIST (src/errno/windows_errno.rs:708), which BASE resolves to "File exists"; ShellMkdirTask::run_from_thread_pool sets err.path to the absolute input (filepath.as_bytes()), which is exactly the target the test asserts. The file sets $.nothrow() globally (line 36), so the un-.nothrow()'d .quiet() call is fine.
  • The source-lint's parseStringMap regex /^\s*"(\w+)" => "([^"\\]*)",/gm won't match rows containing quotes/backslashes, but since the completeness checks require every enum errno to resolve and every DELTA key to be in the divergent set, a row it silently skipped would surface as a failure elsewhere rather than a false pass. parseSystemErrno's E\w+ correctly excludes SUCCESS = 0.
  • The two comment-cop bot comments were addressed in 1b3e619 (both threads resolved). No prior claude[bot] review on this PR.
  • The FreeBSD DELTA is not changed but is now pinned by the same lint (commit 09fdad7); freebsd_errno.rs contains ECAPMODE/EDOOFUS/EINTEGRITY/ENOTCAPABLE, matching the differences table.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant