Skip to content

node: pass ERR_INVALID_ARG_TYPE expected types as lists so fs streams, zlib and diagnostics_channel messages match node - #38657

Open
robobun wants to merge 1 commit into
mainfrom
farm/c8dbe78a/err-invalid-arg-type-lists
Open

robobun wants to merge 1 commit into
mainfrom
farm/c8dbe78a/err-invalid-arg-type-lists

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • Several ERR_INVALID_ARG_TYPE messages thrown from the TypeScript builtins do not match node's text. fs.createReadStream(null, { fd: "x" }) says The "options.fd" property must be of type number or FileHandle. Received type string ('x'); node v26.3.0 says ... must be of type number or an instance of FileHandle. ....
  • Same defect at four more sites, verified against node v26.3.0 (outputs in the details block below):
    • zlib.*Sync(notABuffer): bun of type string, Buffer, TypedArray, DataView, or ArrayBuffer, node of type string or an instance of Buffer, TypedArray, DataView, or ArrayBuffer
    • diagnostics_channel.channel(123) (also subscribe/unsubscribe): bun of type string or symbol, node one of type string or symbol
    • diagnostics_channel.tracingChannel(123): bun must be an string, object, or Channel, node must be of type string or an instance of TracingChannel or Object
    • fs._toUnixTimestamp("x"): bun of type number or Date, node an instance of Date or an Time in seconds
  • Cause: these call sites pass the expected types as one pre-joined string ("number or FileHandle", or ["string, object, or Channel"]), so they hit the single-string overload of the message builder (src/jsc/bindings/ErrorCode.cpp:631), which prints of type + the string verbatim. Node passes an array (['number', 'FileHandle']) and groups the entries. Sites: src/js/internal/fs/streams.ts:173 and :418, src/js/node/zlib.ts:117, src/js/node/diagnostics_channel.ts:222 and :280, src/js/node/fs.ts:1025.

Fix

  • Pass the same arrays node passes at each of the six sites. No error-builder change: the array overload (ErrorCode.cpp:690) is already a port of node's grouping and is what the other ~70 array call sites in src/js use, so the output is node's text byte for byte, including node's an Time in seconds (lib/internal/fs/utils.js#L795) and the TracingChannel entry (lib/diagnostics_channel.js#L315-L317), which are cited at the two sites where the list looks odd.
  • Correct because for node:* modules node's observed output is the contract; every new assertion below is the string real node v26.3.0 prints for the same call.
  • These are the only remaining list-as-string sites in src/js (scanned all 232 $ERR_INVALID_ARG_TYPE calls). The two other joined strings (_http_client.ts:72, tls.ts:1778) already render node's text and are untouched. The "path" site next to the fixed ones in streams.ts is left alone because its real problem is that Buffer paths are rejected, which fs: accept Buffer paths in createReadStream/createWriteStream #35981 fixes.
  • Overlap note: the zlib.ts line and the vendored test restore below are also contained in node: Node-accurate ERR_INVALID_ARG_TYPE / ERR_INVALID_THIS / ERR_MISSING_ARGS; resync test/common and 117 vendored tests with v26.3.0 #35429; they are one line each and rebase cleanly whichever lands first.
  • Verified:
    • test/js/node/fs/fs.test.ts: new options.fd cases for createReadStream, createWriteStream, new fs.ReadStream, new fs.WriteStream (primitive, bigint and object values) and fs._toUnixTimestamp cases (default and explicit name). 21 new tests fail on the unfixed build, pass with the fix; the whole file passes (532 pass, 0 fail).
    • test/js/node/zlib/zlib.test.js: new cases across deflate/gunzip/brotli/zstd/inflateRaw sync methods; fail before, pass after; file passes (392 pass).
    • test/js/node/diagnostics_channel/diagnostics_channel.test.ts: the two existing regexes for the old wording are replaced with node's exact text, plus channel/unsubscribe/tracingChannel cases; 4 fail before, all pass after.
    • test/js/node/test/parallel/test-zlib-not-string-or-buffer.js: restored to the upstream v26.3.0 file byte for byte (it had been edited to bun's wording); exits 1 on the unfixed build, 0 with the fix, 0 under real node.
    • All 35 vendored test-diagnostics-channel-*.js, test-fs-timestamp-parsing-error.js, test-fs-utimes.js and the fd/FileHandle stream tests still exit 0.
  • The same pattern exists in C++ callers of ERR::INVALID_ARG_TYPE (string_decoder, process, crypto); that is a separate change, not touched here.

Background

  • ERR_INVALID_ARG_TYPE message shape: node's lib/internal/errors.js takes the expected types as an array and splits it into primitive type names (of type string, or one of type string or symbol when there are several), class names (an instance of A, B, or C) and free-form phrases, joined with or. A primitive and a class together therefore read of type number or an instance of FileHandle.
  • $ERR_INVALID_ARG_TYPE(name, expected, value) in src/js compiles to a call into src/jsc/bindings/ErrorCode.cpp. If expected is a JS array it goes to the grouping overload described above; if it is a string, the string is printed as-is after of type (or after an when it is a single capitalized phrase). So a list joined in JS skips the grouping, which is the whole bug.
bun 1.4.0 vs node v26.3.0 for the affected calls
fs.createReadStream(null, { fd: "x" })
  bun:  The "options.fd" property must be of type number or FileHandle. Received type string ('x')
  node: The "options.fd" property must be of type number or an instance of FileHandle. Received type string ('x')
fs.createWriteStream(null, { fd: {} })
  bun:  The "options.fd" property must be of type number or FileHandle. Received an instance of Object
  node: The "options.fd" property must be of type number or an instance of FileHandle. Received an instance of Object
zlib.gzipSync(123)
  bun:  The "buffer" argument must be of type string, Buffer, TypedArray, DataView, or ArrayBuffer. Received type number (123)
  node: The "buffer" argument must be of type string or an instance of Buffer, TypedArray, DataView, or ArrayBuffer. Received type number (123)
dc.channel(123)
  bun:  The "channel" argument must be of type string or symbol. Received type number (123)
  node: The "channel" argument must be one of type string or symbol. Received type number (123)
dc.tracingChannel(123)
  bun:  The "nameOrChannels" argument must be an string, object, or Channel. Received type number (123)
  node: The "nameOrChannels" argument must be of type string or an instance of TracingChannel or Object. Received type number (123)
fs._toUnixTimestamp("x")
  bun:  The "time" argument must be of type number or Date. Received type string ('x')
  node: The "time" argument must be an instance of Date or an Time in seconds. Received type string ('x')

With this change every bun line above is identical to the node line.

…ender like node

fs streams (options.fd), zlib's sync convenience methods, diagnostics_channel
channel()/tracingChannel() and fs._toUnixTimestamp passed a pre-joined string
such as "number or FileHandle" as the expected type, so the message read
"must be of type number or FileHandle". Node passes an array and groups the
entries into "of type ..." / "one of type ..." / "an instance of ...".
The array overload of the error builder already does that grouping, so pass
the same arrays node passes.

Restores test-zlib-not-string-or-buffer.js to its upstream wording.
@coderabbitai

coderabbitai Bot commented Aug 14, 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: 31 minutes

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: 0200eb15-05c2-4341-8297-7c6ce483a963

📥 Commits

Reviewing files that changed from the base of the PR and between 97a4363 and f7f823b.

📒 Files selected for processing (8)
  • src/js/internal/fs/streams.ts
  • src/js/node/diagnostics_channel.ts
  • src/js/node/fs.ts
  • src/js/node/zlib.ts
  • test/js/node/diagnostics_channel/diagnostics_channel.test.ts
  • test/js/node/fs/fs.test.ts
  • test/js/node/test/parallel/test-zlib-not-string-or-buffer.js
  • test/js/node/zlib/zlib.test.js

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

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced and fixed, waiting on CI.

  • Reproduced on bun 1.4.0 and main by running fs.createReadStream(null, { fd: "x" }) (plus the zlib, diagnostics_channel and fs._toUnixTimestamp calls listed in the description) under bun and node v26.3.0 and diffing the messages; with this branch the bun output for all of them is identical to node's.
  • Fix and tests are in this PR; every new assertion fails on the unfixed build and passes with it.

@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 — mechanical Node-compat fix that switches six $ERR_INVALID_ARG_TYPE sites from pre-joined strings to arrays so the existing message builder's grouping renders node's exact text.

What was reviewed:

  • Traced each new array through the ErrorCode.cpp array overload (types/instances/other grouping, the object→Object move) and confirmed it produces the exact strings the tests assert.
  • The odd-looking ["Date", "Time in seconds"] and ["string", "object", "TracingChannel"] lists match node's own arrays and yield node's verbatim (if awkward) wording.
  • Existing diagnostics_channel regex assertions were tightened to exact matches, not weakened; the vendored zlib test is restored to upstream.
Extended reasoning...

Overview

Six one-line source changes across src/js/internal/fs/streams.ts (2 sites), src/js/node/diagnostics_channel.ts (2 sites), src/js/node/fs.ts (_toUnixTimestamp), and src/js/node/zlib.ts (zlibBufferSync), each replacing a pre-joined "X or Y" string with the array node passes. Four test files add exact-message assertions and test-zlib-not-string-or-buffer.js is restored to the upstream node file.

I read the ERR_INVALID_ARG_TYPE array overload at src/jsc/bindings/ErrorCode.cpp:690 and traced each new array through it: ["number", "FileHandle"] → one primitive + one class → "of type number or an instance of FileHandle"; ["string", "symbol"] → two primitives → "one of type string or symbol"; ["string", "object", "TracingChannel"] → the special-case at line 718 moves object into instances as Object → "of type string or an instance of TracingChannel or Object"; ["Date", "Time in seconds"] → one class + one free-form phrase → "an instance of Date or an Time in seconds"; ["string", "Buffer", "TypedArray", "DataView", "ArrayBuffer"] → one primitive + four classes. Each matches the test assertions and the node output quoted in the description.

Security risks

None. This only changes the second argument to an error-message helper on already-throwing validation paths. No new inputs are accepted, no parsing, no allocation shape changes.

Level of scrutiny

Low. The change is error-message text only, in the Node compat layer where node's observed output is the contract. The array overload is already the shape used by ~70 other call sites in src/js; nothing in the error builder is being modified.

Other factors

  • Tests are strengthened, not weakened: the two pre-existing diagnostics_channel regexes are replaced with exact code + message matches. The vendored zlib test that had been edited to bun's old wording is restored byte-for-byte to upstream.
  • New tests cover multiple entry points per site (createReadStream/createWriteStream/new fs.ReadStream/new fs.WriteStream, five zlib sync methods, channel/subscribe/unsubscribe/tracingChannel, _toUnixTimestamp with default and custom name), and multiple received-value shapes (primitive, bigint, object, null, undefined) so the "Received …" tail is exercised too.
  • The two source comments citing node source lines are appropriate given the surprising wording ("an Time in seconds") and the TracingChannel entry that isn't obviously right without the reference.
  • The PR notes overlap with #35429 on the zlib line; both are the same one-line change and rebase cleanly.

This branch has not been deployed

No deployments
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