Skip to content

Output.err: print the errno description for libuv errors on Windows - #40852

Closed
robobun wants to merge 4 commits into
mainfrom
farm/aede1369/output-err-libuv-label
Closed

robobun wants to merge 4 commits into
mainfrom
farm/aede1369/output-err-libuv-label

Conversation

@robobun

@robobun robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • On Windows, Output::err(sys_error, ...) for an error built with from_libuv prints ENOENT: <msg> (open) and drops the description (No such file or directory).
  • The ErrName impl for bun_sys::Error (src/sys/Error.rs:524) resolves the tag name through the libuv-aware resolve_system_errno(), but puts the raw stored value (the UV_E* magnitude, 4058) in SysErrInfo.errno. err_with_body (src/bun_core/output.rs:2403) looks the label up with it. On Windows 4058 names UV_ENOENT, which has no coreutils label.

Fix

  • Resolve once with get_error_code_tag_name() and pass the resolved SystemErrno discriminant (2) in SysErrInfo.errno. The Zig Output.err did the same: coreutils_error_map.get(sys_errno) on the resolved errno.
  • Off Windows, and for errors without from_libuv on Windows, the resolved discriminant equals the stored errno. Output does not change there.
  • Adds the bun:internal-for-testing hook sysErrorOutputErr(errno, fromLibuv). It runs Output::err on a constructed error, like sysErrorNameFromLibuv does for name().
  • Verified: test/js/bun/sys/error-name-from-libuv.test.ts on windows-x64. Without the Error.rs change the new test gets ENOENT: sysErrorOutputErr (open), with it ENOENT: No such file or directory: sysErrorOutputErr (open). Also passes on Linux (the from_libuv case skips).

Background

  • On Windows bun_sys::Error.errno holds either an E discriminant (the sync syscall wrappers translate eagerly) or, with from_libuv set (the node_fs async uv callbacks), the negated UV_E* code. resolve_system_errno() reads the flag to tell them apart.
  • SysErrInfo is the view of a bun_sys::Error that bun_core::output::err consumes. bun_core cannot name bun_sys::Error (crate tiers), so bun_sys fills it.
  • bun_core::coreutils_error_map::get(i32) maps a number to its SystemErrno name, then to the glibc text. The Windows SystemErrno enum has a UV_* tail, so 4058 names UV_ENOENT.
Notes
  • In the current tree only the node_fs uv callbacks and Fd::close set from_libuv, and those errors go to JS, not to Output::err. So this is a latent inconsistency found by reading, not a user report. The hook is what makes it testable.
  • Error::name() now shares the UNKNOWN_TAG_NAME constant with the ErrName impl instead of repeating the literal.
  • A related but separate bug: Error::from_uv_rc64 stores the raw UV_E* magnitude with from_libuv unset, so ReturnCodeI64::to_error errors get names like UV_EBADF. That is Report EBADF instead of UV_EBADF for async libuv fs errors on Windows #37465.
  • Report unmapped Windows syscall errors with code "UNKNOWN" instead of "EUNKNOWN" #39340 edits the same test file (the EUNKNOWN expectation). The two changes touch different lines.
  • Clippy is clean for bun_sys, bun_sys_jsc and bun_core on Linux, and for bun_sys_jsc on Windows (bun_sys has pre-existing clippy errors on Windows unrelated to this change).

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/sys/error-name-from-libuv.test.ts

With from_libuv the stored errno is the negated UV code. The ErrName impl
passed it through as-is, so the label table saw UV_ENOENT and the
description was dropped. Resolve once and pass the SystemErrno discriminant.
@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 23 days. After that, they cost $0.25 per reviewed file.

Or wait 4 minutes for your next included review.

View limit details

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f379bd14-f9fa-411c-bd9b-1977eb8ac5fe

📥 Commits

Reviewing files that changed from the base of the PR and between d578a8c and 8271b19.

📒 Files selected for processing (6)
  • src/bun_core/output.rs
  • src/js/internal-for-testing.ts
  • src/runtime/dispatch_js2native.rs
  • src/sys/Error.rs
  • src/sys_jsc/error_jsc.rs
  • test/js/bun/sys/error-name-from-libuv.test.ts

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

@robobun

robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 2:11 PM PT - Aug 28th, 2026

❌ @robobun, your commit 8271b19 has 3 failures in Build #107982 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 40852

That installs a local version of the PR into your bun-40852 executable, so you can run:

bun-40852 --bun

@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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment thread test/js/bun/sys/error-name-from-libuv.test.ts Outdated
Comment thread src/bun_core/output.rs Outdated
Comment thread src/sys/Error.rs Outdated
Comment thread src/sys_jsc/error_jsc.rs Outdated
Comment thread src/sys_jsc/error_jsc.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 — thanks for switching the test helper to return { stdout, stderr, exitCode } and asserting the combined object.

What was reviewed:

  • as_sys_err_info() now resolves via get_error_code_tag_name() and passes the SystemErrno discriminant; the None fallback preserves the old raw-errno behavior, and name() is behavior-identical after the UNKNOWN_TAG_NAME dedup.
  • The test-only host fn: argument validation, u16::try_from bounds, non-Windows early return when from_libuv is true, and #[cfg] gating so from_libuv is used on both platforms without warnings.
  • New tests follow harness conventions (spawn with -e, bunEnv/bunExe, test.concurrent, Promise.all pipe drain, combined-object assertion, Windows-only case skipIf-gated).
Extended reasoning...

Overview

This PR fixes a latent inconsistency in bun_sys::Error::as_sys_err_info(): on Windows, when an error was constructed with from_libuv=true, the raw UV_E* magnitude (e.g. 4058) was passed as SysErrInfo.errno to bun_core::output::err, so the coreutils label lookup missed and the description ("No such file or directory") was dropped from the printed line. The fix resolves once through get_error_code_tag_name() — the same libuv-aware path name() already used — and passes the resulting SystemErrno discriminant instead. A UNKNOWN_TAG_NAME constant deduplicates the fallback literal between name() and as_sys_err_info(). A test-only host function sys_error_output_err(errno, fromLibuv) is added to make this observable, wired through internal-for-testing.ts and dispatch_js2native.rs, and two subprocess-spawning tests are added to the existing error-name-from-libuv.test.ts.

Security risks

None. The change is confined to error-message formatting (which errno discriminant is used to look up a static description string) and a debug-only test hook gated behind bun:internal-for-testing. No user-controlled input reaches new code paths outside tests; no auth, crypto, filesystem, or network surface is touched.

Level of scrutiny

Low-to-moderate. The core change is ~5 lines in Error.rs and is a straightforward "use the already-resolved value instead of the raw stored one" fix. The None arm falls back to i32::from(self.errno), matching prior behavior for the unresolvable case. On non-Windows and for non-from_libuv Windows errors, the resolved discriminant equals the stored errno, so output is unchanged there. The test hook validates argument types and range (u16::try_from), returns early on non-Windows when from_libuv is requested, and the #[cfg(windows)] on the struct field plus the #[cfg(not(windows))] early-return together ensure from_libuv is used on every platform (no unused-variable warning). No CODEOWNERS entries cover the changed paths.

Other factors

My prior inline nit — that the test helper asserted exitCode before returning stderr, hiding the diagnostic on child crash — was addressed in commit 12d97ae: outputErr now returns { stdout, stderr, exitCode } and each test asserts the whole object with .toEqual, per the REVIEW.md convention. Subsequent commits only shortened doc comments. Tests follow the harness patterns from CLAUDE.md (bunExe/bunEnv, -e spawn, test.concurrent for independent subprocesses, Promise.all to drain both pipes, skipIf(!isWindows) for the platform-specific case). The Windows-only assertion relies on CI for verification, which is the normal path for platform-gated tests in this repo.

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI status at 8271b19: every Windows x64 lane passed, which is where the new from_libuv test runs. The red lanes are unrelated to this change and fail on main too:

  • test/js/web/url/url.test.ts (darwin x64, Unicode 16 IDNA table)
  • test/js/node/http2/h2-conformance.test.ts (darwin aarch64)
  • test/cli/run/require-cache.test.ts (debian x64-asan, RSS threshold 321 MB vs 320 MB limit)

The diff only touches error formatting in bun_sys::Error, a test-only hook, and the test file.

@robobun

robobun commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #40860. It removed the from_libuv flag, so bun_sys::Error.errno now always holds an E/SystemErrno discriminant. resolve_system_errno() is a plain SystemErrno::from_repr(self.errno), and as_sys_err_info passes that same value in SysErrInfo.errno. The label lookup in Output::err can no longer disagree with the tag name. The sysErrorNameFromLibuv hook and test/js/bun/sys/error-name-from-libuv.test.ts, which this PR extended, were removed in the same change.

Closing.

@robobun robobun closed this Aug 29, 2026
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.

2 participants