Skip to main content
The repository pins Node.js 26.12.1 in the root .node-version file. It is the 2026-10-21 security release of the Node 26 Current line, which is not LTS. Treat .node-version as the source of the requested version. Installing that version and activating it are separate steps. Version managers need the setup below; a plain node command can still resolve an older installation. The workflow migration and its guard are pending in PR #6496. Until that layer lands, this pin alone does not change CI runtime selection. The CI behavior and receipts described below are the intended acceptance criteria for that layer. The engines.node ranges are support floors, not runtime pins. The root >=22.13.0 floor stays because Vercel maps >=22 to its newest supported major. The >=18.0.0 ranges in published packages are consumer contracts. Raising either range to 26 would break Vercel deployments and package consumers without changing the CI runtime.

Local setup

On a personal machine, install the pinned version side by side, activate it, and verify the version in the same shell that will run your commands. For fnm, initialize the shell and select the version explicitly:
The --use-on-cd hook switches to .node-version when entering the repository. Installing a version without selecting it or enabling that hook is insufficient. mise version-file support is opt-in. On your personal machine, enable Node version files with mise settings add idiomatic_version_file_enable_tools node (this changes mise’s global configuration). With mise’s shell integration active, it then selects the repository’s .node-version. Explicit execution works without relying on shell activation:
nvm does not automatically read .node-version. On a personal machine, run nvm install "$(cat .node-version)", then nvm use "$(cat .node-version)", and check node --version; automatic directory switching requires a separate hook. Volta project pins use the volta field in package.json, which this repository does not supply. Do not assume Volta reads .node-version. Use an explicit one-command override, volta run --node "$(cat .node-version)" node --version, or one of the managers above instead of adding a second repository pin. The devbox bootstrap remains distribution/LTS-based; it does not activate .node-version. Select the private toolchain explicitly when validating on a devbox, just as on other shared hosts.

Shared agent hosts

Several projects share agent hosts, so do not use nvm install there. Placing a build under ~/.nvm/versions/node changes what nvm’s major alias (for example 26) resolves to for every new shell, even when the alias file stays the same. Instead:
  1. Download node-v<version>-<platform>.tar.xz, SHASUMS256.txt and SHASUMS256.txt.sig from https://nodejs.org/dist/v<version>/ into a new empty directory.
  2. Check the SHA-256 of the tarball against SHASUMS256.txt. Verify the signature with the Node.js release keys in a temporary GNUPGHOME.
  3. Extract the tarball to ~/.local/share/tuturuuu/toolchains/node-v<version>-<platform>/.
  4. Call that bin/node by absolute path, or prepend its bin to PATH for your own commands only. For queued work, use ttr resources run -- <toolchain>/bin/node ….
Do not change any of the following, which other sessions and projects share:
  • nvm’s aliases or ~/.nvm;
  • the ttr resources shims;
  • Homebrew or /usr/local Node;
  • runner tool caches;
  • shared setup scripts or shell profiles.
If a change really needs one of these, stop and get the owner’s approval. bun doctor still enforces the Node 22 floor rather than the exact pin.

Where 26.12.1 applies after #6496

A job reads node-version-file only when its checkout is the workflow’s own commit (no ref, or ${{ github.sha }}) and is not sparse. The file then comes from the same tree as the workflow. Other jobs pin the literal 26.12.1:
  • jobs that set up Node before any checkout, such as package publish and dispatch;
  • sparse duplicate-validation checkouts;
  • jobs that check out a PR head, the production branch, a workflow-run head or an input SHA, because that tree may predate .node-version.
The guard proposed in #6496 (scripts/ci/node-version-pin.test.js) requires every literal to equal .node-version and every exception to be listed. It also requires each test "$(node --version)" = v… workflow assertion to name the pin. Each setup step logs the resolved version. Two receipts bind node --version to the exact source SHA:
  • the SDK qualification receipt (runtimeVersions.node with sourceSha);
  • the Calendar native artifact receipt (node with sha).

Intentional exceptions

A floating node-version: 24 does not guarantee the newest patch: setup-node defaults check-latest to false and first accepts a matching cached release. An exact patched version gives a reproducible CI selection; check-latest: true instead requests the newest matching release. Neither proves which patch a deployed function runs.

Native addons

Node 26 uses NODE_MODULE_VERSION 147 and N-API 10. sharp, bcrypt, better-sqlite3, esbuild, lightningcss, @swc/core, @napi-rs/canvas, @napi-rs/keyring and node:sqlite all load and run on 26.12.1. better-sqlite3 is the only one tied to a V8 ABI, so reinstall it after a major Node change.

Updating the pin

  1. Read the target from https://nodejs.org/dist/index.json, including its security and lts fields.
  2. Change .node-version, plus any pre-checkout node-version literals the workflow contract tests list.
  3. Run the root script tests and the affected package tests with that exact binary through ttr resources run.
  4. Check the CI setup logs and the SDK qualification receipt for the new node --version on the exact commit.
  5. Re-check every exception above against its upstream documentation.