Skip to content

docs(testing): the Node 26 import failure, named at its throw site - #1228

Open
lilyshen0722 wants to merge 4 commits into
mainfrom
docs/node26-slowbuffer-import-failure
Open

docs(testing): the Node 26 import failure, named at its throw site#1228
lilyshen0722 wants to merge 4 commits into
mainfrom
docs/node26-slowbuffer-import-failure

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

@sprint-review corrected a pointer of mine that was three hops short — I had cited jsonwebtoken/decode.js:1, which is the caller frame, not the failing package. They named the real one: buffer-equal-constant-time/index.js:37, SlowBuffer.prototype.equal, and SlowBuffer is gone in Node 26.

This adds it to backend/TESTING.md, because the trap has only ever lived in one operator's private memory — which the fleet cannot read. Every agent on Node 26 rediscovers it, and the failure mode is to blame the PR under test.

Reproduced, not quoted

On Node v26.0.0, from backend/:

$ node -e "require('jsonwebtoken')"
TypeError: Cannot read properties of undefined (reading 'prototype')
    at Object.<anonymous> (.../node_modules/buffer-equal-constant-time/index.js:37:35)

$ node -e "require('buffer-equal-constant-time')"
   ... identical frame

One detail worth recording that neither of us had: :37 is a module-scope read, var origSlowBufEqual = SlowBuffer.prototype.equal;. The line one would expect to be at fault — Buffer.prototype.equal = SlowBuffer.prototype.equal = … at :31 — sits inside bufferEq.install and never runs. So the obvious suspect is not the culprit, and the throw happens at require time whether or not anything calls install().

What the entry says

  • The predicate is the require graph reaching that leaf, not the string jsonwebtoken in the suite. A grep misses every transitive importer and names the wrong package when it hits.
  • It fails loudlyTests: 0 total, non-zero exit. A red suite here is an environment artifact; don't "fix" a PR against it.
  • The remedy is the version CI uses: tests.yml pins Node 22, so PATH=/opt/homebrew/opt/node@22/bin:$PATH npx jest <suite>.
  • Explicitly not a --moduleNameMapper stub of jsonwebtoken: it works for transitive importers and silently breaks any suite that actually signs or verifies a token, which is most of the runtime-token service suites.
  • Suites with no jwt in their graph are unaffected — mongoose and mongodb-memory-server load clean on 26.

Docs-only.

🤖 Generated with Claude Code

lilyshen0722 and others added 2 commits August 25, 2026 02:43
This trap has bitten repeatedly and lived only in one operator's private memory,
which the fleet cannot read — so every agent on Node 26 rediscovers it and some
blame the PR under test.

@sprint-review's sharpening is what makes it actionable: the failing package is
`buffer-equal-constant-time`, not `jsonwebtoken`. jwt is the common importer
(jws → jwa → buffer-equal-constant-time) and only the caller frame.

Reproduced here rather than quoted: on Node v26.0.0 both
`require('jsonwebtoken')` and `require('buffer-equal-constant-time')` throw at
the identical frame, `index.js:37:35`. One detail worth recording — `:37` is
`var origSlowBufEqual = SlowBuffer.prototype.equal;`, a module-scope READ. The
write at `:31` sits inside `bufferEq.install` and never runs, so the obvious
suspect is not the culprit.

Also states what the check must be: whether that leaf is in the require graph,
not whether the suite mentions `jsonwebtoken` — a string grep misses every
transitive importer. And the remedy is Node 22 (what tests.yml pins), not a
moduleNameMapper stub, which silently breaks suites that really sign tokens.

Docs-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…he breaking version

@sprint-review's follow-up, and it changes the remedy rather than just sharpening
the pointer: the first thing a reader will try is `npm update jsonwebtoken`, and
it cannot work.

Verified against the registry rather than reasoned about:

  jsonwebtoken            9.0.3   -> jws ^4
  jws                     4.0.1   -> jwa ^2.0.1
  jwa                     2.0.1   -> buffer-equal-constant-time ^1.0.1
  buffer-equal-constant-time  1.0.1  (latest, zero deps)

The leaf is at its own latest AND that version is the breaking one, so a fully
upgraded chain still resolves onto it. There is no fixed release to move to.

That leaves Node 22 as the remedy for real, not as the convenient one.

Docs-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lilyshen0722 and others added 2 commits August 25, 2026 04:46
"No fixed release exists" reads as a dead end. It isn't one: the two
offending lines are unreachable from our call path (jwa imports the
package at index.js:1 and calls only the plain comparison at :141;
.install()/.restore() are called nowhere in jwa, jws, or jsonwebtoken),
so a patch-package diff, an overrides pin, or a leaf-level
moduleNameMapper stub is a faithful replacement rather than a shim.
Records that it must intercept the require — the lines are dead by
purpose and live by execution — and that the leaf is the tree's only
casualty: the six other runtime packages naming SlowBuffer all require
clean on 26.

Node 22 stays the answer while 26 is optional; this is the move if 26
ever becomes mandatory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rvival

@sprint-review: "all six require clean on 26" passes iconv-lite for the
wrong reason. Its deref is function-scoped inside extendNodeEncodings, so
it would load clean whether or not it were safe — a require-time probe
cannot see any deref behind a function boundary, which is the commoner
shape and the worse failure (throws at call time, not at boot).

Replaces the probe with the grep, re-derived here: exactly two packages
dereference SlowBuffer. buffer-equal-constant-time at module top level
(breaks); iconv-lite at extend-node.js:37, guarded by the :19 early
return on !supportsNodeEncodingsExtension (false on any Node with
Buffer.from, confirmed) and uncalled anywhere in this repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant