feat: support Node 26 - #89
Conversation
The import ordering in this file drifted from the prettier-plugin-organize-imports output. The CI check job runs lint, typecheck and test but not format, so the drift was not caught and it blocks the pre-commit hook.
The optimization helpers asked V8 to optimize a function with %OptimizeFunctionOnNextCall without first marking it via %PrepareFunctionForOptimization. V8 used to ignore the missing marker, but since V8 14.6 (Node 26) the mismatch is a fatal CHECK failure that aborts the process with exit code 133. Analysis mode is unaffected because --no-opt discards the optimization request before the check runs, so this only shows up in walltime mode and in uninstrumented runs under the runner's node shim, which still harvests --allow-natives-syntax. Both natives calls have to stay inline in the exported helpers: a shared helper that only touches `fn` inside an eval string looks like it ignores its parameter, and the bundler drops the argument at the call site. Refs COD-3391
Add Node 26 to the CI matrix, ship a prebuild for its ABI (147) and move the pinned toolchain onto it. SUPPORTED_NODE_MAJORS mirrors the build-native-addon targets, so it gains 26 as well. .nvmrc pins 26.8.1 rather than 26.8.0: the 26.8.0 release reports its own version as 26.8.0-alpha.0.0.0, and node-gyp derives the headers URL from process.version, so building the native addon 404s on that release. Fixes COD-3391
e436aa0 to
b414596
Compare
Merging this PR will regress 22 benchmarks
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Memory | switch 2 |
432 B | 13,384 B | -96.77% |
| ❌ | Simulation | switch 1 |
202.1 µs | 694.5 µs | -70.9% |
| ❌ | Simulation | test_recursive_fibo_10 |
277.5 µs | 723.8 µs | -61.65% |
| ❌ | Memory | recursive fibo 15 with hooks |
432 B | 656 B | -34.15% |
| ❌ | Memory | wait 1sec |
14.3 KB | 21.5 KB | -33.41% |
| ❌ | Memory | wait 500ms |
14.3 KB | 21.5 KB | -33.41% |
| ❌ | Memory | wait 500ms |
20.3 KB | 27.5 KB | -25.97% |
| ❌ | WallTime | test sync baz 10 |
108 ns | 144 ns | -25% |
| ❌ | WallTime | switch 2 |
84 ns | 108 ns | -22.22% |
| ❌ | WallTime | test sync baz 10 |
96 ns | 120 ns | -20% |
| ❌ | WallTime | long body |
218.5 µs | 267.5 µs | -18.32% |
| ❌ | WallTime | long body |
218.4 µs | 265.2 µs | -17.62% |
| ❌ | WallTime | long body |
218.6 µs | 264.9 µs | -17.48% |
| ❌ | WallTime | long body |
217.4 µs | 263.4 µs | -17.46% |
| ❌ | WallTime | long body |
218.5 µs | 264.4 µs | -17.36% |
| ❌ | Memory | test_recursive_cached_fibo_10 |
656 B | 784 B | -16.33% |
| ❌ | Memory | test sync baz 10 |
656 B | 784 B | -16.33% |
| ❌ | Memory | short body |
656 B | 784 B | -16.33% |
| ❌ | WallTime | switch 1 |
72 ns | 84 ns | -14.29% |
| ❌ | WallTime | switch 2 |
84 ns | 96 ns | -12.5% |
| ... | ... | ... | ... | ... | ... |
ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing cod-3391-support-nodejs-v26 (b414596) with main (9338d9a)
Greptile SummaryAdds Node.js 26 support across the development toolchain, compatibility CI, native prebuild targets, and supported-version reporting.
Confidence Score: 5/5The PR appears safe to merge, with Node 26 support consistently represented in CI, native prebuild targets, runtime support reporting, and V8 optimization handling. The changed optimization sequence satisfies the newer V8 requirement, its abort-prone behavior is covered in spawned-process tests, and the Node 26 support declarations remain aligned with native build targets and CI coverage.
|
| Filename | Overview |
|---|---|
| packages/core/src/optimization.ts | Adds the V8-required preparation call inline before optimization for both asynchronous and synchronous helpers. |
| packages/core/tests/optimization.integ.test.ts | Verifies both built optimization helpers in child processes with native syntax enabled, allowing fatal V8 failures to surface. |
| packages/core/package.json | Extends native prebuild generation to the Node 26 target while retaining Node 22 and 24 targets. |
| packages/core/src/nodeVersion.ts | Aligns supported-version reporting with the expanded native prebuild target list. |
| .github/workflows/ci.yml | Adds Node 26 to example build and benchmark compatibility coverage. |
| package.json | Updates development Node typings to the Node 26 line without changing the published runtime engine floor. |
| pnpm-lock.yaml | Synchronizes lockfile resolutions for the Node 26 type-definition update. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Node 26 toolchain] --> B[CI compatibility matrix]
A --> C[Native addon build]
C --> D[ABI 147 prebuild]
D --> E[Core native binding]
F[Benchmark callback] --> G[Prepare for optimization]
G --> H[Warm-up calls]
H --> I[Optimize on next call]
I --> J[Measured benchmark execution]
Reviews (1): Last reviewed commit: "feat: support Node 26" | Re-trigger Greptile
Adds Node 26 to the CI matrix, ships a prebuild for its ABI (147), and moves the pinned toolchain onto it.
.nvmrcpins 26.8.1, not 26.8.0The 26.8.0 release reports its own version as
26.8.0-alpha.0.0.0— an upstream packaging slip, since the official binary tarball says the same thing. node-gyp derives the headers URL fromprocess.version, so compiling the native addon against it 404s:That fires during plain
pnpm install, because a package with abinding.gypand noinstallscript gets an implicitnode-gyp rebuild. 26.8.1, released a day later, reports a clean26.8.1and has the same ABI, so pinning it is the whole fix.The CI matrix entry is
"26", which floats to the latest 26.x and so was never affected.V8 14.6 turns a tolerated natives mismatch into a fatal abort
optimizeFunction/optimizeFunctionSyncrequested%OptimizeFunctionOnNextCall(fn)without first marking the function via%PrepareFunctionForOptimization(fn). V8 used to ignore the missing marker; as of V8 14.6 it is a fatal CHECK:The process dies with exit 133. This is mode-dependent, which is why it is easy to miss: analysis mode passes
--no-opt, which discards the optimization request before the check runs. So simulation is fine and walltime aborts — as does any uninstrumented run under the runner'snodeshim, which still harvests--allow-natives-syntax.--no-opt)Unlike the version-string issue this is a real V8 change, and it reproduces on 26.8.1.
Worth a careful look at one detail: both natives calls have to stay inline in the exported helpers. My first attempt factored the new call into a shared helper, and rollup silently dropped the argument at the call site (
prepareForOptimization()), becausefnis only ever referenced inside an eval string. That produced a different fatal CHECK (IsJSFunction(args[0])). There is a comment on the code saying so.The new test in
packages/core/tests/optimization.integ.test.tsspawns a child with--allow-natives-syntaxagainst the built package, since an aborting process cannot be observed from an in-process assertion. It was verified to fail on both bug variants before the fix.Prebuild and supported-version list
build-native-addongains--target 26.0.0for ABI 147.SUPPORTED_NODE_MAJORSmirrors those targets by its own doc comment, so it gains 26 too — otherwise every Node 26 user gets an "unsupported version" annotation.nodeVersion.integ.test.tsasserted that 26 warns, so its cases move accordingly.Verification
Run against real 22.22.2 / 24.19.0 / 26.8.1 toolchains, sequentially:
install --frozen-lockfile,build,lint,typecheck,test, 17/17 bench scripts, and a shim replay using the runner's actualnode.shto confirm the injected V8 flags survive (the no-shim control does report missing flags, so the check is not vacuous). All three core test suites pass on 26.8.1.The install step was re-run with lifecycle scripts explicitly enabled, since that is what exercises the implicit
node-gyp rebuild— the path that produced the 404 above.Two pre-existing items, unchanged here:
benchmark.js-plugin/tests/index.integ.test.tsfails identically on all three versions when the repo is checked out as a git worktree (benchmark URIs pick up the worktree directory name), andpackages/core/src/native_core/linux_perf/utils.hcalls rawv8::String::Utf8Valuein a--no-napiprebuild. The latter only bites when one working tree is reused across majors, because Node 24 does not export the 2-arg constructor; each CI job builds against its own Node, so CI is unaffected.The first commit is an unrelated prettier ordering fix. The
checkjob runs lint, typecheck and test but not format, so the drift went unnoticed and blocked the pre-commit hook.Fixes COD-3391