Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/npm-launcher.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# Single source of truth: .node-version at the repo root. detect.cjs is
# shipped JS that every installing user executes, so the Node it is
# exercised under is a behavioural input, not a toolchain detail. This
# file was created by #6350 on the same day #6367 converted every other
# workflow to the shared pin, so it kept that day's ambient "22.23.1"
# literal by omission rather than by decision.
- uses: actions/setup-node@v7
with:
node-version: "22.23.1"
node-version-file: .node-version
- run: node npm/perry/test/detect.test.cjs
- name: install.sh is valid POSIX sh
run: sh -n packaging/install.sh
Expand All @@ -50,9 +56,13 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# Single source of truth: .node-version. Safe on this image — Node 26
# requires glibc >= 2.28 (nodejs/node v26.x BUILDING.md, Tier 1 linux
# x64/arm64) and jammy ships 2.35. The glibc under test here is the one
# Perry's own prebuilt binary needs, not Node's.
- uses: actions/setup-node@v7
with:
node-version: "22.23.1"
node-version-file: .node-version

- name: Host glibc
run: |
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/release-hono-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,20 @@ jobs:
# DELIBERATE EXEMPTION from the repo-wide .node-version pin: this Node is a
# *publishing* toolchain, not a test oracle — it never runs a parity diff.
#
# Node 24 ships npm 11.x. OIDC Trusted Publishing needs npm >= 11.5.1;
# Node 26 ships npm 11.x. OIDC Trusted Publishing needs npm >= 11.5.1;
# Node 22's npm 10.x silently fails the OIDC handshake and the registry
# returns a misleading 404 on PUT. Do NOT set `registry-url` here — it
# writes a token-based .npmrc that contradicts OIDC (the publish targets
# the default registry.npmjs.org regardless).
Comment on lines +32 to 36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
file=".github/workflows/release-hono-server.yml"
printf '%s\n' "== workflow excerpt =="
sed -n '20,45p' "$file"
printf '%s\n' "== setup-node references =="
rg -n -C 3 'setup-node|registry-url|trusted|OIDC|npm' .github/workflows "$file" 2>/dev/null || true

Repository: PerryTS/perry

Length of output: 50369


🌐 Web query:

actions/setup-node@v7 registry-url npm trusted publishing OIDC documentation

💡 Result:

In actions/setup-node@v7, using Trusted Publishing (OIDC) to publish to npm requires specific configuration to replace long-lived npm tokens with short-lived OIDC tokens [1][2]. Requirements: - npm version: You must use npm ≥ 11.5.1 [2]. - Node.js: Node.js 24 or newer is recommended as it includes a compatible version of npm [2]. - npm Trusted Publisher configuration: You must configure a Trusted Publisher in npm that matches your GitHub repository, workflow, and optional environment [1][2]. Workflow Configuration: To enable OIDC, you must explicitly grant the id-token: write permission in your GitHub Actions workflow [1][2]. Unlike classic token-based authentication, OIDC does not require or use the NODE_AUTH_TOKEN secret during the publish step [3][4]. Example Workflow Snippet: permissions: contents: read id-token: write # Required for OIDC steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: '24' registry-url: 'https://registry.npmjs.org' # Recommended to disable automatic caching if not strictly needed # to mitigate potential cache poisoning risks package-manager-cache: false - run: npm ci - run: npm run build --if-present - run: npm publish Important Considerations: - Avoiding Token Conflicts: In v7.0.0, the action no longer exports a dummy NODE_AUTH_TOKEN [5]. This change prevents issues where the action might inadvertently attempt classic authentication when only OIDC is intended, which previously caused some workflows to fail with ENEEDAUTH or E404 [3][6]. - Security Note: When using Trusted Publishing, it is recommended to set package-manager-cache: false to reduce the risk of cache poisoning, as cached data can expose credentials to attacker-controlled code [1][2]. - Matching Identity: Ensure your GitHub repository, workflow file path, and environment (if used) match exactly what is defined in the npm Trusted Publisher settings, or the publish may fail with an E404 error [1][2].

Citations:


🌐 Web query:

site:docs.npmjs.com trusted publishers GitHub Actions registry-url npm

💡 Result:

When using Trusted Publishers with GitHub Actions to publish packages to npm, you do not need to configure the registry-url in the same way you would with traditional access tokens [1][2]. Trusted publishing uses OpenID Connect (OIDC) to authenticate automatically, eliminating the need for long-lived NPM_TOKEN secrets [2][3]. For your GitHub Actions workflow, follow these requirements: 1. Permissions: You must explicitly grant your workflow the ability to generate an OIDC token by setting the id-token permission to write [1]. permissions: id-token: write contents: read 2. Setup: Use actions/setup-node to prepare your environment. While you can still specify the registry-url if needed for other operations, the publishing step will use the trusted OIDC relationship automatically [1]. - uses: actions/setup-node@v6 with: node-version: '24' registry-url: 'https://registry.npmjs.org' 3. Publishing: Simply run npm publish in your workflow [1]. You do not need to provide an authentication token as an environment variable or via an.npmrc file, as the npm CLI will detect the OIDC token provided by the GitHub Actions environment [1][2]. To configure the trust relationship, use the npm trust command locally on your machine before pushing your workflow: npm trust github [package-name] --file [workflow-filename.yml] --repo [owner/repo] --allow-publish [4][5]. Using Trusted Publishers is the recommended method for publishing from CI/CD, as it improves security by removing the need for long-lived tokens and enables the generation of provenance statements for your packages [2][3].

Citations:


Correct the registry-url comment. registry-url does not contradict npm Trusted Publishing. State that it is unnecessary because registry.npmjs.org is the default registry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-hono-server.yml around lines 32 - 36, Update the
workflow comment near the npm publishing configuration to remove the claim that
registry-url contradicts OIDC or writes a conflicting token-based .npmrc. State
instead that registry-url is unnecessary because registry.npmjs.org is npm’s
default registry, while preserving the existing Node/npm version guidance.

Source: MCP tools

#
# Pinned to a MAJOR literal on purpose, not `node-version-file`: an oracle
# bump made for gap-suite reasons must never be able to move the runtime
# that publishes releases. Registered in
# scripts/check_node_version_consistency.py, which keeps this major in
# step with .node-version's major. Was "24" until the Node-26 sweep.
- uses: actions/setup-node@v7
with:
node-version: "24"
node-version: "26"

- name: Upgrade npm (OIDC Trusted Publisher requires >= 11.5.1)
run: npm install -g npm@latest
Expand Down
19 changes: 15 additions & 4 deletions .github/workflows/release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2034,15 +2034,26 @@ jobs:
# DELIBERATE EXEMPTION from the repo-wide .node-version pin: this Node is a
# *publishing* toolchain (npm registry auth), not a test oracle — it never
# runs a parity diff, so the gap suite's Node version is irrelevant here.
# Pinned to a MAJOR literal on purpose, not `node-version-file`: an oracle
# bump made for gap-suite reasons must never be able to move the runtime
# that publishes releases. Registered in
# scripts/check_node_version_consistency.py, which keeps this major in
# step with .node-version's major.
#
# Was "20" until the Node-26 sweep. Node 20 reached end-of-life on
# 2026-04-30, and this is the repo's most privileged job (`id-token: write`,
# OIDC-publishes every platform package) — an EOL runtime is a defect there.
- uses: actions/setup-node@v7
with:
node-version: "20"
node-version: "26"
registry-url: "https://registry.npmjs.org"

- name: Upgrade npm (OIDC Trusted Publisher requires >= 11.5.1)
# Node 20 ships npm 10.x; versions below 11.5.1 silently fail the OIDC
# handshake and the registry returns a misleading 404 ("'pkg@ver' is
# not in this registry") on PUT. See npm/cli#9088.
# Node 26 already ships npm 11.x, which clears the floor; kept because
# it is free and pins the guarantee rather than inheriting it. Below
# 11.5.1 the OIDC handshake fails silently and the registry returns a
# misleading 404 ("'pkg@ver' is not in this registry") on PUT. See
# npm/cli#9088.
run: npm install -g npm@latest

- name: Download all build artifacts
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,21 @@ jobs:
python3 scripts/check_gc_doc_claims.py --self-test
python3 scripts/check_gc_doc_claims.py

# Node is a correctness input (see CLAUDE.md "TypeScript Parity Status"):
# an oracle that cannot run a gap test drops it from the gate instead of
# failing it. #6367 made `.node-version` the single pin, and the pin has
# leaked twice since -- CLAUDE.md's prose drifted off the file (#7599), and
# npm-launcher.yml was created the same day as #6367 and kept that day's
# ambient "22.23.1" literal by omission. This re-derives every restatement
# from the file it quotes and requires every literal `node-version:` in a
# workflow to be a registered exemption with a reason. Build-free, so it
# belongs in `lint`, which IS a required context.
- name: Node version consistency
if: ${{ !cancelled() }}
run: |
python3 scripts/check_node_version_consistency.py --self-test
python3 scripts/check_node_version_consistency.py

# #7341 layer 3. A RuntimeHandleScope gives an object liveness; it does
# nothing for a raw pointer already read out of the slot. Every rooting bug
# in the quarantine sweep had rooting ALREADY -- what was missing was
Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ Tracked via the gap test suite (`test-files/test_gap_*.ts`). Compared byte-for-b

**The oracle is Node `26.5.1`, pinned in `.node-version` at the repo root** (the FILE is authoritative — this prose drifted once, #7599) — the single source of truth every CI workflow reads via `setup-node`'s `node-version-file`. **Run the gap suite against that exact version locally**, or your results won't match CI. The version is a *correctness input*, not an incidental toolchain detail: when node can't run a test (a feature newer than the pinned node), node exits non-zero, the harness classifies it `node_fail`, and the test is **silently dropped from the gate** rather than going red. CI sat on Node 22 while the suite grew Node 24/26 features, which hid 14 tests — all of Temporal, plus DisposableStack, Float16Array, and `Uint8Array` base64/hex (#6364). Node patch releases also change observable output (error-message text, `v8` heap fields), which is why the pin is exact. Raising it is a deliberate act: measure the failure delta under both oracles first, then triage what it exposes.

Two workflows are deliberately exempt and say so inline: `node-core-subset.yml` derives its Node from `test-compat/node-core/pinned-version.txt` (it runs Node's *own* test corpus, which must match its own Node line), and the two release workflows use Node purely as an npm *publishing* toolchain.
**Don't audit this by reading prose — run `python3 scripts/check_node_version_consistency.py --list`** (it is a `lint` step, so it is a required check). It re-derives every restatement of a Node version from the file that restatement quotes, and requires every literal `node-version:` in a workflow to be a *registered* exemption carrying a reason. That registry is the authoritative exemption list; the three below are what it currently holds, and an entry that stops matching the tree fails, so a bump must update or delete its own exemption.

Three workflows are deliberately exempt and say so inline: `node-core-subset.yml` derives its Node from `test-compat/node-core/pinned-version.txt` (it runs Node's *own* test corpus, which must match its own Node line), and the two release workflows (`release-packages.yml`, `release-hono-server.yml`, both Node 26) use Node purely as an npm *publishing* toolchain — pinned to a bare major on purpose, so a gap-suite oracle bump can never move the runtime that publishes releases. A fourth file, `benchmarks/public-baseline-config.json`, still pins **Node v22.23.1** for the *published* performance comparison: it is in `public_baseline.HARNESS_PATHS`, so editing it invalidates the committed artifact and reddens `lint` until the baseline is regenerated (~2 h on the quiet mini). The pin and the measurement are atomic by design — see the exemption's own text for the runbook.

**Last full sweep:** run `./run_parity_tests.sh` for the current snapshot. The umbrella tracker is #793 (Node.js + TypeScript compatibility roadmap); the previously-cited #447–#452 batch closed on 2026-05-04. Currently-open trackers worth knowing about:

Expand Down
19 changes: 19 additions & 0 deletions changelog.d/7967-node26-everywhere.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
**Every Node the project chooses is now 26, and one file is asserted to be the source (#7967).** `.node-version` (26.5.1) has been the authoritative oracle since #6367, but the pin leaked twice: CLAUDE.md's prose drifted off the file (#7599), and `.github/workflows/npm-launcher.yml` was created by #6350 on the *same day* #6367 converted every workflow that existed, keeping that day's ambient `"22.23.1"` literal by omission rather than by decision. CLAUDE.md compounded it by saying "**Two** workflows are deliberately exempt" and then listing three, which is why the fourth pin read as accounted-for.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the release-note claim.

“Every Node the project chooses is now 26” is false. This PR deliberately retains Node 22 for test-compat/node-core/pinned-version.txt and benchmarks/public-baseline-config.json. State that Node 26 is the default workflow and oracle policy, with documented Node 22 exemptions.

Based on learnings: “describe the final shipped behavior as one coherent release-note entry.”

🧰 Tools
🪛 LanguageTool

[uncategorized] ~1-~1: The official name of this software platform is spelled with a capital “H”.
Context: ...prose drifted off the file (#7599), and .github/workflows/npm-launcher.yml was created...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@changelog.d/7967-node26-everywhere.md` at line 1, Revise the release-note
entry to state that Node 26 is the default workflow version and authoritative
oracle, while explicitly documenting Node 22 exemptions for
test-compat/node-core/pinned-version.txt and
benchmarks/public-baseline-config.json. Describe the final behavior coherently
without claiming every project-selected Node version is 26.

Source: Learnings


Changed:

- **`npm-launcher.yml`** (both jobs): `"22.23.1"` → `node-version-file: .node-version`. This one is behaviour-sensitive, not a toolchain: the `detect-self-test` job runs `node npm/perry/test/detect.test.cjs`, exercising `npm/perry/bin/detect.cjs` — shipped JS that every installing user executes. Safe on the `ubuntu-22.04` glibc-2.35 job: Node 26 requires glibc ≥ 2.28 (nodejs/node `v26.x` BUILDING.md, Tier 1 for linux x64/arm64).
- **`release-packages.yml`**: `"20"` → `"26"`. Node 20 reached end-of-life on 2026-04-30, and this is the repo's most privileged job (`id-token: write`, OIDC-publishes every platform package) — an EOL runtime there is a defect independent of the sweep.
- **`release-hono-server.yml`**: `"24"` → `"26"`.

Both release workflows stay pinned to a bare **major literal** rather than `node-version-file`, deliberately: they are npm *publishing* toolchains, and a gap-suite oracle bump made for parity reasons must never be able to move the runtime that publishes releases. Their `npm install -g npm@latest` step is kept — Node 26 already clears the OIDC ≥ 11.5.1 floor, but pinning the guarantee beats inheriting it.

New **`scripts/check_node_version_consistency.py`**, wired as a step in `lint` (a required context), following the `check_gc_doc_claims.py` pattern: it re-derives every restatement of a Node version from the file that restatement quotes (CLAUDE.md ×2, `llms.txt`, `test-parity/node-compat-matrix.baseline.json`), and requires every literal `node-version:` under `.github/workflows/` to be a *registered exemption carrying a reason*. A rewording that removes a marker fails rather than silently ceasing to be checked; exemptions are asserted against the tree, so one that stops matching fails and must be updated or deleted. It deliberately does **not** assert that `.node-version` and `external-tools.json`'s pin are equal — CLAUDE.md documents the compat-matrix oracle as independent, so encoding that coupling would be wrong. `--self-test` proves each rule can fail (including both vacuity floors); reverting `npm-launcher.yml` to `"22.23.1"` reproduces the historical bug as two named failures.

**Not changed, and this is the substantive finding: `benchmarks/public-baseline-config.json` still pins `v22.23.1`.** Perry's *published* performance comparison is measured against a runtime four majors old (pre-existing — #6376, regenerated #7285; #7958 only made it declarative, which is how it became visible). It cannot be edited on its own: the file is in `public_baseline.HARNESS_PATHS`, so its bytes feed the committed artifact's `harness_fingerprint`, and `validate_public()` separately compares the config against the version recorded in `benchmarks/results/public-node-bun-v1.json`. Measured rather than assumed — flipping the pin to `v26.5.1` and running the gate exits **2** with `public artifact benchmark harness changed`, and that gate is the "Public benchmark evidence freshness" step of **`lint`**. Shipping the pin edit alone would therefore not be "stale but honest"; it would be a required gate red for `main` and every open PR. The pin and its ~2 h measurement are atomic by design (#7282/#7958).

It is instead registered as a **self-clearing exemption** carrying the runbook: regenerate on `perry@perry-macos.local` (the Apple M1 / 8 GB mini recorded in the artifact's `host` block — the artifact pins host identity, so regenerating elsewhere replaces the baseline rather than updating it), ~2 h across five components, each gated on ≤ 25% CPU active for 60 consecutive seconds. When that happens the config value moves, the exemption stops matching, and the check fails until the entry is deleted — so the stale pin cannot quietly become a fossil. **Expect the regeneration to reduce Perry's published advantage**: Node 26 ships a materially newer V8 than Node 22. The magnitude was not measured, because the box available for this work has run at load 30–200 all day and cannot satisfy the artifact's own quiet-host policy; a number produced there would be junk wearing a decimal point.

Also corrected: `external-tools.json` instructed readers to bump "the `NODE_PIN.version` constant in `scripts/node_compat_matrix.mjs`", which does not exist — that script reads `external-tools.json` itself.

Out of scope: `bun: 1.3.14` in the same config is **not** equally stale (1.3.x is the current Bun line), but it is frozen by the same atomicity rule and should be re-pinned in the same regeneration run.
2 changes: 1 addition & 1 deletion external-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"repository": "github:nodejs/node",
"distBaseUrl": "https://nodejs.org/dist",
"notes": [
"Latest CURRENT stable at pin time. Bump via the NODE_PIN.version constant in scripts/node_compat_matrix.mjs, then run --update-baseline and review the diff.",
"Latest CURRENT stable at pin time. Bump by editing tools.node.version here (plus each platform asset name and its sha512 SRI), then run scripts/node_compat_matrix.mjs --update-baseline and review the diff. There is no NODE_PIN constant in that script — it reads this file; an earlier revision of this note said otherwise.",
"Assets are the official nodejs.org dist tarballs. Integrity is sha512 recomputed from the downloaded bytes (matching this file's sha512 SRI convention); nodejs.org also publishes sha256 in SHASUMS256.txt, cross-checked at pin time against every asset here.",
"Installed by scripts/node_compat_matrix.mjs via its own resolver (download + SRI-verify + cache under .cache/node-pin/), NOT the shared tool rack: nodejs.org is not a github release host, and the tarball is a full node tree rather than a single bin.",
"Official dist ships no musl build (musl lives on unofficial-builds.nodejs.org); alpine/musl runners fall back to a system node.",
Expand Down
Loading
Loading