Skip to content

node: give seven ERR_* codes Node's message text - #43502

Merged
dylan-conway merged 1 commit into
mainfrom
robobun/6efc8472/node-error-code-messages
Sep 19, 2026
Merged

dylan-conway merged 1 commit into
mainfrom
robobun/6efc8472/node-error-code-messages

Conversation

@robobun

@robobun robobun commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Seven Node error codes have a broken .message. http.request({ headers: { Trailer: "X-T" } }).end() throws ERR_HTTP_TRAILER_INVALID with the message undefined. url.fileURLToPath(httpUrl, { windows: true }) throws ERR_INVALID_URL_SCHEME with the message file. Notes list all seven.
  • The cause is the tail of 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.
  • The reverse also happens. A sentence passed to a templated code gives Cannot call Stream is destroyed after a stream was destroyed.

Fix

  • Four codes get Node's template: ERR_HTTP_TRAILER_INVALID, ERR_SCRIPT_EXECUTION_INTERRUPTED, ERR_HTTP_CONTENT_LENGTH_MISMATCH, ERR_INVALID_URL_SCHEME. The server strictContentLength check throws from Rust (NodeHTTPResponse.rs). It now formats Node's sentence with both byte counts.
  • The call sites of three codes now pass Node's argument: ERR_STREAM_DESTROYED("write"), ERR_OPERATION_FAILED("write failed after retries"), ERR_METHOD_NOT_IMPLEMENTED("FileHandle with fs"). Dead arguments are gone.
  • Verified: 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.
  • Self-reviewed: 12 concerns raised, 8 addressed, 4 rejected (see Notes).

Background

  • Built-in JS writes $ERR_FOO(a, b). The build turns it into a call of jsFunctionMakeErrorWithCode, which builds the message in C++.
  • A message template is a case in that function, or a row in its simpleErrorMessages table: fixed text around one or two arguments.
  • strictContentLength makes node:http throw when the body size differs from Content-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.

code call before after
ERR_HTTP_TRAILER_INVALID client request with a Trailer header and no chunked body undefined Trailers are invalid with this transfer encoding
ERR_HTTP_CONTENT_LENGTH_MISMATCH client req.end("abc"), Content-Length: 5 3 Response body's content-length of 3 byte(s) does not match the content-length of 5 byte(s) set in header
ERR_HTTP_CONTENT_LENGTH_MISMATCH server res.end("abc"), Content-Length: 5 Content-Length mismatch the same sentence
ERR_INVALID_URL_SCHEME fileURLToPath(httpUrl, { windows }) file The URL must be of scheme file
ERR_STREAM_DESTROYED res.destroy(); res.write("x", cb) Cannot call Stream is destroyed after a stream was destroyed Cannot call write after a stream was destroyed
ERR_SCRIPT_EXECUTION_INTERRUPTED REPL, Ctrl+C during await undefined Script execution was interrupted by `SIGINT`
ERR_OPERATION_FAILED FileHandle writer, every write returns 0 bytes Operation failed: Operation failed: write failed after retries Operation failed: write failed after retries
ERR_METHOD_NOT_IMPLEMENTED createReadStream(null, { fd: fileHandle, fs }) The fs.FileHandle with custom fs operations method is not implemented The FileHandle with fs method is not implemented
  • How the list was made: a sweep over src/js for every $ERR_X( call, split by whether X has a case or a table row in ErrorCode.cpp. 57 codes have no template. All of them pass a full sentence, except the four above and ERR_HTTP2_UNSUPPORTED_PROTOCOL. The reverse direction (a sentence passed into a template) gave ERR_STREAM_DESTROYED and ERR_OPERATION_FAILED. ERR_METHOD_NOT_IMPLEMENTED passes a fragment, but not the one Node passes.
  • The ported test-fs-read-stream-file-handle.js gets its upstream message: assertion back. It was commented out because of the ERR_METHOD_NOT_IMPLEMENTED text.
  • Deleted arguments: the sentence in two $ERR_HTTP_TRAILER_INVALID(...) calls in _http_server.ts, and the ...args of the REPL wrapper for ERR_SCRIPT_EXECUTION_INTERRUPTED. The constant-message case ignores 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 read The URL must be of scheme The URL must be of scheme file.
  • src/js/builtins.d.ts declares the argument shapes of the four codes that got a template. Without a declaration the code generator emits (message: string).
  • Not in this PR: ERR_HTTP2_UNSUPPORTED_PROTOCOL (http2.connect("ftp://...") prints ftp:) has the same cause. A separate change owns it (branch robobun/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 edits makeSimpleErrorMessage and 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.
  • Not in this PR: fetch() also throws ERR_HTTP_CONTENT_LENGTH_MISMATCH (FetchTasklet.rs) with its own sentence about the request body. That is a Bun fetch error, not a node:http one, so its text stays.
  • Not in this PR: fs.readFileSync(new URL("http://example.com")) throws ERR_INVALID_URL_SCHEME from src/runtime/node/types.rs with the text URL must be a non-empty "file:" path. ERR_INVALID_FILE_URL_PATH and ERR_INVALID_FILE_URL_HOST share that same sentence there. A separate change fixes the three together, because the path and host texts need more than a new literal.
  • Three of the five ERR_OPERATION_FAILED call sites doubled the prefix, all in the FileHandle writer. The other two already pass Node's argument. The test reaches the synchronous writer site: it replaces fs.writeSync with a function that returns 0, and the writer looks writeSync up on the public module at call time. The two async sites bind write and writev at 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.
  • The REPL test asserts that the output contains the message, not the whole line. Bun prints Uncaught Error: <message> where Node prints Uncaught: and the inspected error with its [ERR_...] bracket. That difference is about the stack header, not the message.
  • Node does not check the first res.write() against Content-Length (its _contentLength is still null at that point). Bun's native check does. The tests use a second write, which both runtimes reject with 6 byte(s).
  • Found on the way and not changed here (node errors: four ERR_* message templates differ from Node's text #43520). Four templates in ErrorCode.cpp differ from Node's own text: ERR_HTTP_SOCKET_ASSIGNED (Socket already assigned, Node: ServerResponse has an already assigned socket), ERR_TLS_INVALID_PROTOCOL_VERSION and ERR_TLS_PROTOCOL_VERSION_CONFLICT (Node formats the values with %j, so it prints "TLSv9" is not a valid minimum TLS protocol version), and ERR_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's lib/internal/errors.js. 108 are identical, and these four differ.
  • Found on the way and not changed here (node:http: strictContentLength on a ServerResponse checks different writes than Node #43519). The server strictContentLength check differs from Node in behavior: Content-Length: 0 is never checked, the first res.write() is checked (Node checks from the second write on), and a string header is parsed with parseInt where Node uses +value.
  • Self-review, the four concerns I did not act on. (1) Fix the types.rs arm of ERR_INVALID_URL_SCHEME here: a separate change fixes the three arms together. (2) Add the ERR_HTTP2_UNSUPPORTED_PROTOCOL row here: a separate change owns it. (3) Fix ERR_HTTP_SOCKET_ASSIGNED and the Content-Length parsing 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, and test/CLAUDE.md says not to set one: node:repl takes 5 to 9 s to load on a debug build with ASAN, and test/js/bun/repl/repl.test.ts uses the same value for the same reason.
  • Earlier work: node: fix broken error messages for ERR_HTTP2_UNSUPPORTED_PROTOCOL and 4 others #35791 covered five of these codes and was closed as stale with conflicts, not on its merits. node:http: fix ERR_HTTP_CONTENT_LENGTH_MISMATCH and ERR_HTTP_TRAILER_INVALID message text #35777 covered two. This PR follows the review threads of node: fix broken error messages for ERR_HTTP2_UNSUPPORTED_PROTOCOL and 4 others #35791: the server path, the REPL code, and the dead arguments.
  • Suites run on the debug build: 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 ported test-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. In node-http.test.ts, should propagate exception in sync data handler timed out once in the full run and passes alone in 3 s.

`$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.
@robobun
robobun requested a review from alii as a code owner September 19, 2026 18:44
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Warning

Review paused — included plan limit reached

Keep your review moving with free on-demand reviews.

  • Run this review for free

On-demand reviews are free for one more day.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Promotion and pricing details

On-demand reviews are free for one more day. After that, they cost $0.25 per reviewed file.

Review limit details

Or wait 3 minutes for your next included review.

Check out review usage here.

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: oven-sh/bun/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 5126afdc-dd20-4f42-9d59-5ae92ba42309

📥 Commits

Reviewing files that changed from the base of the PR and between 26e7a4b and 47bb4a1.

📒 Files selected for processing (11)
  • src/js/builtins.d.ts
  • src/js/internal/fs/streams.ts
  • src/js/internal/http.ts
  • src/js/internal/repl/node-errors.js
  • src/js/node/_http_server.ts
  • src/js/node/fs.promises.ts
  • src/js/node/url.ts
  • src/jsc/bindings/ErrorCode.cpp
  • src/runtime/server/NodeHTTPResponse.rs
  • test/js/node/errors/error-code-messages.test.ts
  • test/js/node/test/parallel/test-fs-read-stream-file-handle.js

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

@robobun

robobun commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:02 PM PT - Sep 19th, 2026

⏳ @robobun, your commit 47bb4a1 is still building in Build #118416, but has 1 failures so far (All Failures):

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

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator Author

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));
}
bun node
ERR_HTTP_TRAILER_INVALID "undefined" "Trailers are invalid with this transfer encoding"
ERR_INVALID_URL_SCHEME "file" "The URL must be of scheme file"

USE_SYSTEM_BUN=1 bun test test/js/node/errors/error-code-messages.test.ts fails 7 of 8 tests. bun bd test test/js/node/errors/error-code-messages.test.ts passes all 8.

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