Repository navigation
Conversation
…mes as properties fs.writeFile, writeFileSync, appendFile, appendFileSync, fs.promises.writeFile, fs.promises.appendFile and FileHandle#appendFile rejected a non-boolean flush with 'The "flush" argument must be of type boolean'. Node validates the option as options.flush, so its message is 'The "options.flush" property must be of type boolean'. The option name now matches node, and the Rust ERR_INVALID_ARG_TYPE renderers in JSGlobalObject apply the same subject rule the C++ renderer already uses: a name containing "." is a property, anything else is an argument. The other dotted names passed to these renderers (fs.rm retry options, net.SocketAddress options, http2 priority options) now match node as well.
|
Warning Review limit reached
Next review available in: 28 seconds 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. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
Comment |
|
Status: reproduced on the released build (1.4.0) with Self-review outcome: the only concern was packaging. The |
There was a problem hiding this comment.
LGTM — small Node-compat fix that names options.flush correctly and makes the Rust ERR_INVALID_ARG_TYPE renderers apply the same argument/property rule the C++ side already uses.
What was reviewed:
InvalidArgTypeName::fmtmatchesBun::Message::addParameterin ErrorCode.cpp exactly (argumentsuffix → verbatim; contains.→ property; else → argument); undotted names render byte-identical to before.strings::contains_charexists inbun_core::string::immutableand is already in scope.- New tests cover all 7 flush entry points (sync/cb/promise, path/fd, string/Buffer), the accepted-values path, a dotted validator name (
rmSyncoptions.maxRetries), and a plain name (fsyncSyncfd) — all needed imports already present in the file.
Extended reasoning...
Overview
Two source changes and one test file. src/runtime/node/node_fs.rs renames the flush option error subject from "flush" to "options.flush" at the single shared parse site. src/jsc/JSGlobalObject.rs adds a private InvalidArgTypeName Display wrapper and routes the three Rust ERR_INVALID_ARG_TYPE renderers (throw_invalid_argument_type_value, ..._value2, ..._one_of) through it so a dotted name renders as "x.y" property and a name already ending in argument renders verbatim, matching Node and matching what Bun::Message::addParameter in ErrorCode.cpp already does for the C++/JS-builtin path. test/js/node/fs/fs.test.ts gains two describe blocks pinning the message text across every writeFile/appendFile entry point plus a dotted and a plain name through other renderers.
Security risks
None. Pure error-message formatting; no parsing of untrusted input, no allocation changes, no FFI signature changes, no new syscalls.
Level of scrutiny
Low-to-moderate. The node_fs.rs hunk is a one-string change. The JSGlobalObject.rs hunk changes shared renderers, so the blast-radius question is whether any existing caller depended on the old wording. I confirmed the new Display impl is byte-identical to the old output for every name that lacks a . (the else branch emits exactly "\"{name}\" argument"), so only dotted names change — and for those the new output is what Node produces. The PR description enumerates every dotted caller and states no test pinned the old text; the author also re-ran the plain-name pinning tests. The logic mirrors the existing C++ addParameter I read in ErrorCode.cpp:632-642 line for line.
Other factors
Tests are thorough and follow harness conventions (tempDir with using, fd closed in finally, it.each matrix, awaited .rejects, negative assertion that the file was not written). All identifiers used in the new test code (tempDir, writeFileSync, openSync, closeSync, readFileSync, promises, rmSync, join, fs) are already imported at the top of the file. strings::contains_char is a real helper in bun_core::string::immutable.rs and strings is already in the use list. No prior human or bot review comments to address; CI is building. This is a focused, well-verified compat fix and I am comfortable approving.
Problem
fs.writeFileSync(p, "ab", { flush: "yes" })throwsERR_INVALID_ARG_TYPEwith the messageThe "flush" argument must be of type boolean. Received type string ('yes'). Node v26.3.0 throws the same code withThe "options.flush" property must be of type boolean. Received type string ('yes').args::WriteFile::from_js_with_default_flag):fs.writeFile,writeFileSync,appendFile,appendFileSync,fs.promises.writeFile,fs.promises.appendFile,FileHandle#appendFile.src/runtime/node/node_fs.rs:4203passes the bare nameflush. Node validates the option asoptions.flush(validateBoolean(flush, 'options.flush')inlib/fs.jsandlib/internal/fs/promises.js).JSGlobalObject::throw_invalid_argument_type_value(src/jsc/JSGlobalObject.rs:559) hardcodesThe "<name>" argument, so even a dotted name renders as an argument. Other callers already pass dotted names and hit the same thing:fs.rmSync(p, { maxRetries: "a" })andnew net.SocketAddress({ address: 1 })say"options.maxRetries" argument/"options.address" argumentwhere node saysproperty.test-fs-write-file-flush.js,test-fs-append-file-flush.js) only check the code, which was already right, so they did not catch it.Fix
node_fs.rs: the flush error names the optionoptions.flush.JSGlobalObject.rs: the three RustERR_INVALID_ARG_TYPErenderers (throw_invalid_argument_type_value,..._value2,..._one_of) format the name throughInvalidArgTypeName, which applies node's subject rule:"x" argument,"a.b" property, or the name verbatim when it already ends inargument. This is the ruleBun::Message::addParameterinErrorCode.cppalready applies to every C++ caller, so the Rust and C++ renderers now agree.node:fserrors node's text is the contract (jsc: use 'property' wording in get_optional_slice type error #32355 landed the same argument-to-property correction for another Rust helper), and these messages are byte-identical to node v26.3.0. A script exercising the 7 flush entry points (string and Buffer data, path and fd targets),rmSyncretry options andSocketAddressoptions diffs clean between this build and a localnode, apart from things this PR leaves alone:FileHandle#writeFiledoes not forwardflushon main (fs.promises: validate options.flush for iterable data and FileHandles, sync like node #39192 adds it), thesignaloption error (node:fs: validate options.signal in readFile, writeFile and watch like node #39274), and themkdir/readdirrecursiveandrmrecursive/forceerrors, which go through two other helpers and are tracked separately..change output. The dotted names currently passed to these renderers areoptions.flush(this PR),options.retryDelay/options.maxRetries(fs.rm),options.address/options.flowlabel(net.SocketAddress),options.silent/endStream/exclusive/parent/weight/signal(http2 priority and request options),handle.fd(cluster), and Bun's owncompress.encoding(fetch) andrule.syscall/rule.action(socket fault injection). All of the node ones render aspropertyin node; no test in the repo pinned the old wording for any of them. No Rust caller passes a name ending inargument. Names without a dot render exactly as before.JSGlobalObject.rshunk is the one from node: render the Rust validators' ERR_INVALID_ARG_TYPE messages like Node #38663 (validators message parity), and node:fs: validate options.signal in readFile, writeFile and watch like node #39274 (options.signal, which edits the sameWriteFileparser a few lines above this hunk) carries it verbatim too. Trial merges of this PR with either of them, and of all three together, are conflict-free and end up with a singleInvalidArgTypeName; whichever lands first carries the renderer change and the others reduce to their own call sites. Neither of them touches the flush name. This stays a separate PR because it only changes text, while node:fs: validate options.signal in readFile, writeFile and watch like node #39274 also changes which values are accepted.test/js/node/fs/fs.test.ts: newwriteFile/appendFile flush optionblock covers all 7 entry points (path and fd, string and Buffer) for threeReceivedshapes, plus the accepted values; a second block pins a dotted name reached through the validators (rmSyncoptions.maxRetries) and a plain name (fsyncSyncfd) still rendering as an argument. 7 of the 9 new tests fail on the released build, all pass with this change; the whole file passes (521 pass).test-crypto-random.jsandtest/js/web/fetch/wasm-streaming.test.ts(..._value2),test/js/node/child_process/child_process-node.test.js(..._one_of).test-fs-write-file-flush.js,test-fs-append-file-flush.js,test-fs-rm.js,test-socketaddress.js, andtest/js/node/net/socketaddress.spec.ts,test/js/web/fetch/fetch-compress.test.tspass.Background
ERR_INVALID_ARG_TYPE(name, expected, actual)in node'slib/internal/errors.jsrendersThe <subject> must be <expected>. Received <description of actual>. The subject is"name" argument, unless the name contains a., in which case it is an option path such asoptions.flushand renders as"name" property; a name that already ends inargument(such asfirst argument) is used verbatim.ErrorCode.cpp, used by the JS builtins through$ERR_INVALID_ARG_TYPEand by C++ bindings) and the Rust helpers onJSGlobalObjectused by natively implemented modules. TheReceived ...half is shared (the Rust helpers call the C++determineSpecificType), which is why only the subject differed.