Repository navigation
Node 20.20.2 node:test crashes on non-ASCII startup logs, not the named test #158
Description
Summary
On Node 20.20.2, npm run test:node can fail a file at :1:1 with Unable to deserialize cloned data due to invalid or unsupported version. The stack is Node's test-runner parser. The named file is the worker whose stdout pipe carried the bytes, not a failed assertion. The trigger is already on main: boot logs write non-ASCII to stdout, and those bytes can land in the same read as a report frame. A green rerun is not a repair.
On current main the node:test step is report-only (continue-on-error: true at .github/workflows/test.yml:244). Run 35908127184 failed because that PR's workflow made the suite a Node 20 gate without continue-on-error. Removing the report-only line on main would turn this into a red check.
Reproduction
This is the file that failed 2 of 8 runs on Node 20.20.2. Node 26.9.0 passed 4 of 4 runs of the same file. One isolated write of the banner, with no surrounding tests, passed on both.
const test = require('node:test');
const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
test('ordinary assertion before console banner', async () => {
await tick();
});
test('console.log writes the AGNT banner between runner frames', async () => {
for (let i = 0; i < 40; i++) {
console.log(`📁 AGNT root: /tmp/agnt-example ${i}`);
await tick();
}
});
test('ordinary assertion after console banner', async () => {
await tick();
});Run node --test console-banner.test.js. A failing run looks like this:
not ok 1 - console-banner.test.js
location: '.../console-banner.test.js:1:1'
failureType: 'uncaughtException'
error: 'Unable to deserialize cloned data due to invalid or unsupported version.'
stack: |-
#proccessRawBuffer (node:internal/test_runner/runner:358:20)
FileTest.parseMessage (node:internal/test_runner/runner:294:28)
Socket.<anonymous> (node:internal/test_runner/runner:393:15)
Those three stack lines match run 35908127184, which blamed tests/unit/asyncToolsToggle.test.js:1:1.
A separate write crashes on the first run on both Node 20.20.2 and Node 26.9.0: fs.writeSync(1, Buffer.from([0xff, 0x0f, 0x00, 0x00, 0x00, 0x08, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48])). I am not claiming the hosted job wrote those exact bytes.
Root cause
node --test reads each worker's stdout as V8 frames. A frame starts with FF 0F and a 4-byte size. Bytes before that header are printed as text. On Node 20.20.2, #proccessRawBuffer then reads the four bytes after FF 0F as a signed length, with no >>> 0, and readHeader() throws when that length is negative. A UTF-8 lead byte is >= 0x80. The banner does not itself contain FF 0F. It crashes when one of those lead bytes is spliced into that length field in the same stdout read as a report frame. That is why one isolated write passed and the interleaved run failed 2 of 8.
Node 26.9.0 has the unsigned read, so this banner case passed there. It still crashes on the explicit FF 0F write. That remaining case is nodejs/node#66164 and nodejs/node#66273. Related: nodejs/node#64061 and nodejs/node#65934.
On main at 1b103e62, tests/unit/asyncToolsToggle.test.js does not import the database directly. It imports backend/src/services/orchestrator/tools.js (line 22), which imports AuthManager.js (line 24), which imports backend/src/models/database/index.js (line 1). That import boots PathManager. The boot logs are:
backend/src/utils/PathManager.js:74when root and data are the same directory, and:76plus:77when they differ. The hosted log printed the:76line,📁 AGNT root:.backend/src/models/database/index.js:634is one✓ Added ${col.name} column...log. The hosted flood also matches the other checkmark logs in that file (39 calls). A stderr change has to cover that whole set, not only line 634.
Suggested fix
Do not treat the named test as the failing assertion. Do not treat a green rerun, a serialized run, or filtering this error text as a repair.
- In this repo, send the boot logs above to stderr while
node --testis running. The runner reads stderr as text and does not deserialize it. Leave assertion failures and nonzero exits alone. A test that still writesFF 0Fto stdout will keep crashing. That is the Node bug, and hiding it would be wrong. - The parser repair is the open Node change above. Do not drop the Node 20 job, and do not treat a Node 26 pass as proof that 20.20.2 is fixed.
Environment
- agnt
mainat1b103e62 - Node 20.20.2, the version
actions/setup-nodeacquired fornode-version: 20in run 35908127184 - Node 26.9.0 as the control
- Linux x64
Activity
agnt-gg commentedon Oct 2, 2026
Fixed by #159, which is now in our 0.6.7 release branch: boot logs go to stderr during
node:testruns, so the non-ASCII bytes can no longer land in the worker's report frames. Thanks @tryingET for the precise diagnosis. Closing.— Annie