Repository navigation
node: give seven ERR_* codes Node's message text - #43502
Conversation
`$ERR_*(...)` in built-in JS goes through `jsFunctionMakeErrorWithCode`. A code with no `case` and no `simpleErrorMessages` row uses its first argument as the whole message. A code that has a template wraps whatever it gets. - ERR_HTTP_TRAILER_INVALID and ERR_SCRIPT_EXECUTION_INTERRUPTED get their constant messages. The ClientRequest and REPL call sites pass no argument, so the message was "undefined". The two ServerResponse call sites passed the sentence. That argument is dead now and is removed. - ERR_HTTP_CONTENT_LENGTH_MISMATCH and ERR_INVALID_URL_SCHEME get table rows. The call sites that pass Node's template arguments printed "3" or "file". fileURLToPathBuffer passed the whole sentence and now passes "file" too. The server side of the content-length check throws from NodeHTTPResponse.rs and now formats the same text. - ERR_STREAM_DESTROYED (ServerResponse) and ERR_OPERATION_FAILED (FileHandle writer) passed a sentence into a template. They now pass Node's argument. - ERR_METHOD_NOT_IMPLEMENTED (FileHandle stream with a custom fs) passed a different fragment than Node. It now passes "FileHandle with fs", and the ported test-fs-read-stream-file-handle.js asserts the message again. - builtins.d.ts declares the argument shapes of the four codes that got a template.
|
Warning Review paused — included plan limit reachedKeep your review moving with free on-demand reviews.
On-demand reviews are free for one more day.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Promotion and pricing detailsOn-demand reviews are free for one more day. After that, they cost $0.25 per reviewed file. Review limit detailsOr wait 3 minutes for your next included review. Limit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository: oven-sh/bun/.coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (11)
Comment |
|
Updated 12:02 PM PT - Sep 19th, 2026
⏳ @robobun, your commit 47bb4a1 is still building in
|
|
Status: ready for review. PR: #43502 How I reproduced it, with bun 1.4.3-canary.1+367d939d9 and node v26.3.0: const http = require("node:http");
const url = require("node:url");
const req = http.request({ host: "127.0.0.1", port: 1, agent: false, headers: { Trailer: "X-T" } });
req.on("error", () => {});
try {
req.end();
} catch (e) {
console.log(e.code, JSON.stringify(e.message));
}
req.destroy();
try {
url.fileURLToPath("http://example.com/x", { windows: true });
} catch (e) {
console.log(e.code, JSON.stringify(e.message));
}
|
Problem
.message.http.request({ headers: { Trailer: "X-T" } }).end()throwsERR_HTTP_TRAILER_INVALIDwith the messageundefined.url.fileURLToPath(httpUrl, { windows: true })throwsERR_INVALID_URL_SCHEMEwith the messagefile. Notes list all seven.jsFunctionMakeErrorWithCode(src/jsc/bindings/ErrorCode.cpp:2478). A code with no message template uses its first argument as the whole message. These call sites pass Node's template arguments, or nothing.Cannot call Stream is destroyed after a stream was destroyed.Fix
ERR_HTTP_TRAILER_INVALID,ERR_SCRIPT_EXECUTION_INTERRUPTED,ERR_HTTP_CONTENT_LENGTH_MISMATCH,ERR_INVALID_URL_SCHEME. The serverstrictContentLengthcheck throws from Rust (NodeHTTPResponse.rs). It now formats Node's sentence with both byte counts.ERR_STREAM_DESTROYED("write"),ERR_OPERATION_FAILED("write failed after retries"),ERR_METHOD_NOT_IMPLEMENTED("FileHandle with fs"). Dead arguments are gone.test/js/node/errors/error-code-messages.test.ts. Bun 1.4.3-canary.1+367d939d9 fails 7 of its 8 tests. The expected messages are node v26.3.0's.Background
$ERR_FOO(a, b). The build turns it into a call ofjsFunctionMakeErrorWithCode, which builds the message in C++.casein that function, or a row in itssimpleErrorMessagestable: fixed text around one or two arguments.strictContentLengthmakesnode:httpthrow when the body size differs fromContent-Length. The client checks in JS. The server checks in native code, which never reaches the C++ template.Notes
Before and after, per code. The "after" text is identical to node v26.3.0.
ERR_HTTP_TRAILER_INVALIDTrailerheader and no chunked bodyundefinedTrailers are invalid with this transfer encodingERR_HTTP_CONTENT_LENGTH_MISMATCHreq.end("abc"),Content-Length: 53Response body's content-length of 3 byte(s) does not match the content-length of 5 byte(s) set in headerERR_HTTP_CONTENT_LENGTH_MISMATCHres.end("abc"),Content-Length: 5Content-Length mismatchERR_INVALID_URL_SCHEMEfileURLToPath(httpUrl, { windows })fileThe URL must be of scheme fileERR_STREAM_DESTROYEDres.destroy(); res.write("x", cb)Cannot call Stream is destroyed after a stream was destroyedCannot call write after a stream was destroyedERR_SCRIPT_EXECUTION_INTERRUPTEDawaitundefinedScript execution was interrupted by `SIGINT`ERR_OPERATION_FAILEDFileHandlewriter, every write returns 0 bytesOperation failed: Operation failed: write failed after retriesOperation failed: write failed after retriesERR_METHOD_NOT_IMPLEMENTEDcreateReadStream(null, { fd: fileHandle, fs })The fs.FileHandle with custom fs operations method is not implementedThe FileHandle with fs method is not implementedsrc/jsfor every$ERR_X(call, split by whetherXhas acaseor a table row inErrorCode.cpp. 57 codes have no template. All of them pass a full sentence, except the four above andERR_HTTP2_UNSUPPORTED_PROTOCOL. The reverse direction (a sentence passed into a template) gaveERR_STREAM_DESTROYEDandERR_OPERATION_FAILED.ERR_METHOD_NOT_IMPLEMENTEDpasses a fragment, but not the one Node passes.test-fs-read-stream-file-handle.jsgets its upstreammessage:assertion back. It was commented out because of theERR_METHOD_NOT_IMPLEMENTEDtext.$ERR_HTTP_TRAILER_INVALID(...)calls in_http_server.ts, and the...argsof the REPL wrapper forERR_SCRIPT_EXECUTION_INTERRUPTED. The constant-messagecaseignores them.fileURLToPathBuffer(url.ts:1320) passed the whole sentence and was correct. With the new table row it passes"file", like its sibling and like Node. Without that edit the message would readThe URL must be of scheme The URL must be of scheme file.src/js/builtins.d.tsdeclares the argument shapes of the four codes that got a template. Without a declaration the code generator emits(message: string).ERR_HTTP2_UNSUPPORTED_PROTOCOL(http2.connect("ftp://...")printsftp:) has the same cause. A separate change owns it (branchrobobun/d80742c6/http2-unsupported-protocol), so this PR does not add its row. node errors: render %s codes like util.format and keep the sign of a received -0 #43087 also editsmakeSimpleErrorMessageand adds a row at the end of the table. The new rows here are in the middle of the table to keep the merges clean.fetch()also throwsERR_HTTP_CONTENT_LENGTH_MISMATCH(FetchTasklet.rs) with its own sentence about the request body. That is a Bunfetcherror, not anode:httpone, so its text stays.fs.readFileSync(new URL("http://example.com"))throwsERR_INVALID_URL_SCHEMEfromsrc/runtime/node/types.rswith the textURL must be a non-empty "file:" path.ERR_INVALID_FILE_URL_PATHandERR_INVALID_FILE_URL_HOSTshare that same sentence there. A separate change fixes the three together, because the path and host texts need more than a new literal.ERR_OPERATION_FAILEDcall sites doubled the prefix, all in theFileHandlewriter. The other two already pass Node's argument. The test reaches the synchronous writer site: it replacesfs.writeSyncwith a function that returns 0, and the writer lookswriteSyncup on the public module at call time. The two async sites bindwriteandwritevat module load, so a test cannot make them return 0. They get the same change. Node's own writer calls its binding directly, so this expected string comes from Node's source ('Operation failed: %s'with'write failed after retries') and not from a Node run. Every other expected string is Node's output for the same call.Uncaught Error: <message>where Node printsUncaught:and the inspected error with its[ERR_...]bracket. That difference is about the stack header, not the message.res.write()againstContent-Length(its_contentLengthis still null at that point). Bun's native check does. The tests use a second write, which both runtimes reject with6 byte(s).ErrorCode.cppdiffer from Node's own text:ERR_HTTP_SOCKET_ASSIGNED(Socket already assigned, Node:ServerResponse has an already assigned socket),ERR_TLS_INVALID_PROTOCOL_VERSIONandERR_TLS_PROTOCOL_VERSION_CONFLICT(Node formats the values with%j, so it prints"TLSv9" is not a valid minimum TLS protocol version), andERR_IPC_CHANNEL_CLOSED(Channel closed., Node has no period). Those are wrong templates, not call sites that miss a template. A script compared the 118 constant and table messages with the literal templates in Node'slib/internal/errors.js. 108 are identical, and these four differ.strictContentLengthcheck differs from Node in behavior:Content-Length: 0is never checked, the firstres.write()is checked (Node checks from the second write on), and a string header is parsed withparseIntwhere Node uses+value.types.rsarm ofERR_INVALID_URL_SCHEMEhere: a separate change fixes the three arms together. (2) Add theERR_HTTP2_UNSUPPORTED_PROTOCOLrow here: a separate change owns it. (3) FixERR_HTTP_SOCKET_ASSIGNEDand theContent-Lengthparsing here: they are a different class, tracked in node errors: four ERR_* message templates differ from Node's text #43520 and node:http: strictContentLength on a ServerResponse checks different writes than Node #43519. (4) The REPL test passes a 20 s timeout, andtest/CLAUDE.mdsays not to set one:node:repltakes 5 to 9 s to load on a debug build with ASAN, andtest/js/bun/repl/repl.test.tsuses the same value for the same reason.error-code-messages.test.ts,test/js/node/url/url-fileurltopath*.test.*,node-http-transfer-encoding.test.ts,node-http.test.ts,test/js/node/fs/promises.test.js, and the portedtest-http-content-length-mismatch.js,test-http-server-de-chunked-trailer.js,test-http-set-trailers.js,test-url-fileurltopath.js,test-fs-whatwg-url.js,test-fs-read-stream-file-handle.js,test-repl-sigint.js,test-repl-sigint-nested-eval.js,test-worker-unsupported-path.js. Innode-http.test.ts,should propagate exception in sync data handlertimed out once in the full run and passes alone in 3 s.