41. Upgrade Node runtime to 24 LTS
Date: 2026-06-17
Status
Accepted
Context
The project was pinned to Node 20 in several places: .nvmrc (20),
package.json engines (>=20), the devcontainer image
(typescript-node:20), and prose in README.md, CLAUDE.md, AGENTS.md, and
docs/go-live.md. CI consumes the version via actions/setup-node with
node-version-file: .nvmrc, so .nvmrc is the single source of truth for the
build/test runtime.
Two forces made this stale:
- Node 20 'Iron' has reached end-of-maintenance (~April 2026). As of June 2026 it no longer receives security patches. For an application handling PII and payroll/financial data — where this repo's stated posture is "security and correctness over speed" — running an EOL runtime is itself a risk.
- Modern tooling now expects a newer runtime (e.g. the Impeccable design skill requires Node ≥24), and the local development default had already been moved to Node 24 via Volta and nvm.
We considered three targets:
- Node 22 'Jod' (Maintenance LTS) — supported but already in maintenance; upgrading to a soon-older line buys little.
- Node 26 (latest Current, v26.3.0) — newest overall, but not LTS until ~October 2026. A Current release carries a ~6-month support window and no LTS stability/security guarantees. Inappropriate for a production financial app.
- Node 24 'Krypton' (Active LTS, v24.16.0) — the current Active LTS through ~October 2026, with maintenance support continuing into 2027. Already the local default; satisfies the Node ≥24 tooling floor.
Decision
Standardize the project on Node 24 LTS, pinned to 24.16.0.
.nvmrc→24.16.0(CI follows automatically vianode-version-file).package.jsonengines.node→>=24; add a Volta pinvolta.node = 24.16.0so Volta auto-selects the right runtime inside this repo (matching the wider org convention of pinning Node via Volta).- Devcontainer image →
mcr.microsoft.com/devcontainers/typescript-node:24. - Update the Node version stated in
README.md,CLAUDE.md,AGENTS.md, anddocs/go-live.md.
The patch version is pinned exactly (not 24 or lts/*) so CI, local, and
container builds are reproducible — important for an auditable payroll system.
Patch bumps (e.g. 24.16.0 → 24.x.y for a security release) are therefore a
deliberate, reviewed change to .nvmrc + volta.node, not automatic.
packageManager stays pnpm@9.12.0; no dependency changes are required (Next
16, React 19, Vitest 2, Biome 1.9, TypeScript 5 all support Node 24).
Consequences
- Positive: back on a supported, security-patched LTS line; local, CI, and container runtimes are aligned to one exact version; unblocks Node ≥24 tooling.
- Deploy follow-up (required): the host (Vercel) selects its runtime from its
own project setting /
engines.node. The Vercel project's Node.js version must be set to 24.x; verify in the Vercel dashboard so production matches CI. Tracked indocs/go-live.mdstep 3. - Contributor impact: developers need Node 24 locally.
nvm use(reads.nvmrc) or Volta (readsvolta.node) handle this automatically; the only manual step is installing 24 once. - Maintenance: exact pinning trades automatic patch uptake for reproducibility — security patch bumps must be applied manually. Revisit the target when Node 24 leaves Active LTS (~Oct 2026) or when 26 enters LTS.