Node error messages print a received -0 as 0 at several sites (Node prints -0)
Author: robobunCreated Sep 17, 2026Updated Sep 17, 2026
Node prints negative zero with its sign when it writes a received value into an error message. It uses util.inspect, or the %s of util.format, and both print -0. Bun prints 0 at the sites below. A differential run against Node v26.3.0 found them. No user reported them.
#43069 fixes the shared C++ helper JSValueToStringSafe (ERR_OUT_OF_RANGE, ERR_INVALID_ARG_VALUE and the %s codes that pass the value to it). The sites in this issue do not reach that helper with the number, so they stay wrong after #43069.
Repro
Run with bun and with node:
const crypto = require("crypto"), http = require("http"), cp = require("child_process"), http2 = require("http2");
const { StringDecoder } = require("string_decoder");
const { Readable } = require("stream");
const rows = {
"Buffer#toString": () => Buffer.alloc(1).toString(-0),
"StringDecoder": () => new StringDecoder(-0),
"Readable#setEncoding": () => new Readable().setEncoding(-0),
"crypto.randomInt": () => crypto.randomInt(-0),
"crypto.pbkdf2Sync": () => crypto.pbkdf2Sync("a", "b", -0, 1, "sha1"),
"process.chdir": () => process.chdir(-0),
"http2.getUnpackedSettings": () => http2.getUnpackedSettings(-0),
"http.validateHeaderName": () => http.validateHeaderName(-0),
"spawnSync killSignal": () => cp.spawnSync("true", [], { killSignal: -0 }),
};
for (const [k, f] of Object.entries(rows)) {
try { f(); console.log(k, "no throw"); } catch (e) { console.log(k, "|", e.code, "|", e.message); }
}| call | Node v26.3.0 | Bun 1.4.3-canary (main b64b63069c) |
|---|---|---|
Buffer.alloc(1).toString(-0) |
Unknown encoding: -0 |
Unknown encoding: 0 |
new StringDecoder(-0) |
Unknown encoding: -0 |
Unknown encoding: 0 |
new Readable().setEncoding(-0) |
Unknown encoding: -0 |
Unknown encoding: 0 |
crypto.randomInt(-0) |
... greater than the value of "min" (0). Received -0 |
Received 0 |
crypto.pbkdf2Sync("a", "b", -0, 1, "sha1") |
... Received -0 |
Received 0 |
process.chdir(-0) |
Received type number (-0) |
Received type number (0) |
http2.getUnpackedSettings(-0) |
Received type number (-0) |
Received type number (0) |
http.validateHeaderName(-0) |
Header name must be a valid HTTP token ["-0"] |
["0"] |
child_process.spawnSync("true", [], { killSignal: -0 }) |
Unknown signal: -0 |
Unknown signal: 0 |
Where each one comes from
Unknown encoding: 0:src/jsc/bindings/JSBuffer.cpp(lines 327, 682, 1853) andsrc/jsc/bindings/JSStringDecoder.cpp:589calltoStringon the value and pass the string toERR::UNKNOWN_ENCODING. The overload that takes theJSValuerenders more shapes like Node, but it is not a drop-in change: a value with its owntoStringprints differently there. https://github.com/oven-sh/bun/pull/43069#issuecomment-5714589841 compares nine values on both routes.crypto.randomInt:src/runtime/node/node_crypto_binding.rs:355convertsmaxtoi64before it formats the message.crypto.pbkdf2Sync: thef64case of the Rustout_of_rangeformatter (src/bun_core/fmt.rs:3384). #40737 fixes it.Received type number (0): the number case ofdetermineSpecificType(src/jsc/bindings/ErrorCode.cpp). #38642 fixes it.src/js/node/http2.ts:3855has a JS copy of that function with the same defect.http.validateHeaderName: the table of fixed-template codes (makeSimpleErrorMessageinErrorCode.cpp) usesToStringfor each argument.killSignal:ERR_UNKNOWN_SIGNALinsrc/js/node/child_process.ts:1959is a JS template string.
Source: oven-sh/bun