Skip to content

Enforce the Node pin: xtask preflight, engine-strict, docs (bd-lh30hlvd) - #664

Merged
cscheid merged 4 commits into
mainfrom
feature/bd-lh30hlvd-node-version-guard
Sep 8, 2026
Merged

Enforce the Node pin: xtask preflight, engine-strict, docs (bd-lh30hlvd)#664
cscheid merged 4 commits into
mainfrom
feature/bd-lh30hlvd-node-version-guard

Conversation

@cscheid

@cscheid cscheid commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

The repo has pinned Node 24 since ca6d47c8 (.nvmrc + engines.node), but the pin was advisory: .nvmrc needs a version manager, engines only warns at npm install, and xtask never looked. A brew upgrade on 2026-09-04 relinked node to 26.8.1 on a dev machine and the hub-client vitest suite went red four days later with 23 unrelated-looking localStorage failures (Node ≥ 25 defines a localStorage accessor that vitest 4.x's jsdom environment refuses to overwrite; fixed upstream only in vitest 5).

Decision: keep engines.node at ^24.0.0 for now and make the pin enforced.

  • crates/xtask/src/node_version.rs (new): reads engines.node from the root package.json, probes node --version, classifies the result. Ranges use npm's own grammar (nodejs-semver) — Cargo's semver crate rejects the space-separated >=24 <25 form. An unparsable range is an error, never a silent pass. 17 unit tests, written first.
  • cargo xtask verify: "Preflight: Node toolchain" before Step 1 — fails in seconds on a mismatch or missing node with an actionable message; prints the version found on success; skips itself when every npm-driven step is disabled. Q2_ALLOW_NODE_MISMATCH=1 warns and continues (deliberate experiments only).
  • cargo xtask dev-setup: warn-only check with per-platform fnm install hints.
  • .npmrc: engine-strict=truenpm install/npm ci fail with EBADENGINE under a Node outside the range (verified under 26.8.1) and succeed under 24 (fresh npm ci).
  • Docs: claude-notes/instructions/node-version.md (the pin, what enforces it, fnm setup incl. why ~/.zprofile not ~/.zshenv on macOS, the Homebrew relink trap, bump procedure); pointers from CLAUDE.md and .claude/rules/worktrees.md.

Investigation record, timeline and fix-candidate table: claude-notes/plans/2026-09-08-node26-vitest-localstorage.md. Deferred vitest 5 / shim work for the eventual Node 26 bump: bd-s84z961e.

Verification

  • Full cargo xtask verify under Node 24.20.0: all 14 steps green (13,757 Rust tests; hub-client 1084/119/133; trace-viewer, shared preview-*, hub MCP suites).
  • Under Node 26.8.1, cargo xtask verify stops at the preflight with exit 1 before any Rust build.
  • Clippy (-D warnings), cargo fmt, cargo xtask lint clean.

CI runs actions/setup-node with node-version: '24' in every workflow, so engine-strict and the preflight are no-ops there — this PR is mainly to confirm that.

🤖 Generated with Claude Code

https://claude.ai/code/session_017Gr8MUcZtbVAz8wzokihQV

cscheid and others added 4 commits September 8, 2026 15:05
…nv skips it

Root cause: Node >= 25 defines a `localStorage` accessor on globalThis that
returns undefined without --localstorage-file; vitest 4.x's jsdom environment
skips window keys already present on the global unless allowlisted, and the
allowlist lacks localStorage/sessionStorage (fixed upstream only in vitest 5).
Recurrence of the May 2026 incident (ca6d47c): the Node 24 pin is advisory
(no version manager, engine-strict off) and a Sep 4 `brew upgrade` relinked
node to 26.8.1.

Plan skeleton + probe, prototype setupFiles shim (62/62 affected tests green
on Node 26), and fix/enforcement design questions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Gr8MUcZtbVAz8wzokihQV
Decision (2026-09-08): keep engines.node at ^24.0.0, fix the machine (fnm
from .nvmrc), and make the pin enforced (xtask verify/dev-setup check,
engine-strict). Defer the vitest 5 / jsdom-Storage shim to the LTS bump.
Phase A (machine) is done and recorded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Gr8MUcZtbVAz8wzokihQV
The repo has pinned Node 24 since ca6d47c (.nvmrc + engines.node), but the
pin was advisory: `.nvmrc` needs a version manager, `engines` only warns at
`npm install`, and xtask never looked. A `brew upgrade` on 2026-09-04
relinked `node` to 26.8.1 and the hub-client vitest suite went red four days
later with 23 unrelated-looking `localStorage` failures (Node >= 25 defines a
localStorage accessor that vitest 4.x's jsdom environment refuses to
overwrite; fixed upstream only in vitest 5).

Decision: keep engines.node at ^24.0.0 and make the pin enforced instead.

- crates/xtask/src/node_version.rs (new): reads engines.node from the root
  package.json, probes `node --version`, and classifies the result. Ranges are
  interpreted with npm's own grammar (nodejs-semver) — Cargo's semver crate
  rejects the space-separated `>=24 <25` form. An unparsable range is an
  error, never a silent pass. 17 unit tests, written first.
- cargo xtask verify: "Preflight: Node toolchain" before Step 1 — fails in
  seconds on a mismatch or missing node with an actionable message, prints the
  version it found on success, skips itself when every npm-driven step is
  disabled. Q2_ALLOW_NODE_MISMATCH=1 warns and continues (deliberate
  experiments only).
- cargo xtask dev-setup: warn-only check with fnm install hints per platform.
- .npmrc: engine-strict=true, so `npm install`/`npm ci` fail with EBADENGINE
  under a Node outside the range (verified under 26.8.1) and succeed under 24
  (fresh `npm ci` in the worktree).
- claude-notes/instructions/node-version.md: the pin, what enforces it, fnm
  setup (why ~/.zprofile rather than ~/.zshenv on macOS), the Homebrew relink
  trap, and how to bump the pin (incl. the vitest 5 / shim item, bd-s84z961e).
  Pointers from CLAUDE.md and .claude/rules/worktrees.md.

Full `cargo xtask verify` under fnm's Node 24.20.0: all 14 steps green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Gr8MUcZtbVAz8wzokihQV
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Gr8MUcZtbVAz8wzokihQV
@posit-snyk-bot

posit-snyk-bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cscheid
cscheid merged commit df7fc3a into main Sep 8, 2026
10 checks passed
@cscheid
cscheid deleted the feature/bd-lh30hlvd-node-version-guard branch September 8, 2026 21:58
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.

2 participants