Skip to content

ci: move every action to the node24 runtime and pin by SHA - #330

Merged
thecodedrift merged 3 commits into
mainfrom
fix/npm-always-auth-warning
Sep 11, 2026
Merged

ci: move every action to the node24 runtime and pin by SHA#330
thecodedrift merged 3 commits into
mainfrom
fix/npm-always-auth-warning

Conversation

@thecodedrift

Copy link
Copy Markdown
Member

GitHub removes the Node 20 action runtime from hosted runners on September 23, 2026 (changelog). Five of the actions the publish workflows pin by SHA are old enough to declare runs.using: node20, and the release pipeline is what breaks on that date. This PR moves every uses: in .github/workflows to the latest release of its major, pinned by full commit SHA, and confirms from each pinned action.yml that the runtime is node24. The always-auth warning from #294 falls out of the same bump.

Three commits, meant to be read in order:

  1. Pins. Every action, every workflow, no floating @vN tag left.
  2. A note beside each OIDC publish step recording why setup-node v7 is a floor there, so the pin is not lowered again.
  3. .nvmrc. node-version: 24 was repeated thirteen times; it now lives in one file.

Runtime table

Resolved with gh api repos/<owner>/<repo>/git/ref/tags/<tag>, annotated tags dereferenced to the commit, and runs.using read from action.yml at both refs.

Action Old ref New ref Runtime before Runtime after
actions/checkout 34e11487 (v4.3.1), and floating @v4, @v6 3d3c42e5aac5ba805825da76410c181273ba90b1 (v7.0.1) node20 (@v6 was node24) node24
actions/setup-node 49933ea5 (v4.4.0), and floating @v6 820762786026740c76f36085b0efc47a31fe5020 (v7.0.0) node20 (@v6 was node24) node24
pnpm/action-setup b906affc (v4.3.0), and floating @v6 ea17c68df8912ef543352723c149a84f56e3d413 (v6.1.0) node20 (@v6 was node24) node24
actions/github-script floating @v7 3a2844b7e9c422d3c10d287c895573f7108da1b3 (v9.0.0) node20 node24
actions/upload-artifact ea165f8d (v4.6.2) 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a (v7.0.1) node20 node24
actions/download-artifact d3f86a10 (v4.3.0) 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c (v8.0.1) node20 node24
changesets/action a45c4d59 (v1.9.0, commented # v1) unchanged, comment now # v1.9.0 node24 node24
anthropics/claude-code-action d40ddef4 (v1.0.195, commented # v1) 0a8d3c9443bbff909ab973b6a17a340b913f229f (v1.0.221) composite (N/A); its one inner step, oven-sh/setup-bun v2.2.0, is node24 composite (N/A)

No action lacked a node24 release. changesets/action is the one not moved to its latest major; see below.

What each major crossing was checked against

  • checkout v7 refuses to check out fork PR code under pull_request_target and workflow_run (src/unsafe-pr-checkout-helper.ts). The only workflow_run checkouts here are in release-cli-nightly.yml, which gates on workflow_run.event == 'push' before any job runs; the guard itself returns before looking at the ref unless the triggering event was a pull_request*. openspec-label.yml checks out a PR head, but under plain pull_request. The on-demand review checks out refs/pull/N/head under issue_comment. None of these is in the guarded set.
  • setup-node v5 auto-caches when package.json names a package manager; v6 narrowed that to npm only. This workspace names pnpm@10.12.4, so nothing changes; the jobs that want a cache already say cache: pnpm.
  • download-artifact v5 changed the extraction path only for single downloads by ID; release-vale.yml downloads by name. v8 fails on digest mismatch by default, which is the behaviour we want.
  • upload-artifact v7 keeps if-no-files-found and include-hidden-files; the new archive input defaults to the old behaviour.
  • github-script v9 drops require('@actions/github') and injects getOctokit. The scripts here use github.rest.*, github.paginate, and relative require of .cjs files, all of which v9 still provides (src/wrap-require.ts is unchanged in shape).
  • pnpm/action-setup v6 adds pnpm 11/12 support; still reads packageManager.

The always-auth warning (#294)

The job log for the linked run shows the line first under npm install -g npm@12.0.1 (emitted by the runner's bundled npm 11.19.0) and then once per npm publish under npm 12.0.1. Nothing in this repo writes the option. actions/setup-node v4.4.0's src/authutil.ts appends always-auth=${input} to the .npmrc it creates whenever registry-url is set; the log's step header shows the input defaulting to always-auth: false. Reproduced locally: an .npmrc with that line produces the identical warning under npm 11.9.0.

setup-node v6.1.0 removed the line (actions/setup-node#1436); the v7.0.0 authutil.ts writes only _authToken=${NODE_AUTH_TOKEN} and registry=. So the pin bump in commit 1 is the fix, and commit 2 only records it.

OIDC shape, verified after the change

For each of the three publish jobs (release-cli.yml, release-vale.yml, release-cli-nightly.yml): id-token: write present, registry-url present, --provenance on the publish, --ignore-scripts on the installs, and no NODE_AUTH_TOKEN or NPM_TOKEN anywhere in the workflow. Two things improved as a side effect of v7:

  • v4 through v6 exported a dummy NODE_AUTH_TOKEN=XXXXX-XXXXX-XXXXX-XXXXX into every later step (visible in the linked log's env: blocks). v7.0.0 removed that (actions/setup-node#1558, motivated by OIDC publishing). The "No token in env" comment in release-cli.yml is now literally true.
  • npm 12's lib/utils/oidc.js exchanges the GitHub ID token and then config.set(<registry>:_authToken, token, 'user'), so the OIDC token overrides whatever .npmrc holds. With the dummy gone, .npmrc yields a literal ${NODE_AUTH_TOKEN} string (npm's env-replace leaves unresolved references as-is rather than throwing). Measured locally under both .npmrc shapes: public npm view returns 0 for a real version and 1 for a missing one, so the npm view idempotency guard in front of each publish keeps working.

Not changed, worth a decision

  • changesets/action stays on v1.9.0. It is already node24 and the latest v1. v2.0.0 renames every input we pass (version to version-script, commit to commit-message, title to pr-title), drops the GITHUB_TOKEN env in favour of a github-token input, removes its .npmrc handling, and moves to Changesets v3 packages while this repo is on @changesets/cli ^2.29.8. That is a migration of release-cli-changeset.yml, not a pin, and it cannot be exercised on a PR. Separate change.
  • npm install -g npm@12.0.1 in all three publish jobs: npm 12 still emits the "next major" warning for always-auth, so it is npm 13 that refuses. No other deprecated config keys or npm config set calls exist in the workflows.
  • actionlint is not installed here (no binary, no gh extension), so it was not run. pnpm lint, pnpm test:scripts (377 tests, including the committed-workflows test) and prettier --check on the workflows all pass.
  • .nvmrc says 24, the Active LTS line. Node 26 becomes LTS on 2026-10-28; that is a one-line change when it is time. engines.node in packages/cli/package.json is untouched.

Fixes #294

@thecodedrift
thecodedrift marked this pull request as ready for review September 11, 2026 00:47
GitHub removes the Node 20 action runtime on September 23, 2026. Every
`uses:` in .github/workflows now points at the full commit SHA of the
latest release of its major, and each pinned action.yml was read to
confirm `runs.using: node24` (composite actions have no runtime).

Before this change the publish workflows pinned actions/checkout v4.3.1,
actions/setup-node v4.4.0, pnpm/action-setup v4.3.0, upload-artifact
v4.6.2 and download-artifact v4.3.0, all node20, and the remaining
workflows floated on `@v4`, `@v6` and `@v7` tags.

Bumps crossing a major were checked against the release notes:

- checkout v7 refuses fork PR checkouts under pull_request_target and
  workflow_run. The only workflow_run checkouts here (release-cli-nightly)
  are gated on `event == 'push'` first, and the guard returns before it
  looks at the ref unless the triggering event was a pull_request.
- setup-node v5/v6 auto-cache only when package.json names npm; this
  workspace names pnpm, so nothing changes. v6.1 stops writing
  `always-auth` to .npmrc and v7 stops exporting a dummy NODE_AUTH_TOKEN.
- download-artifact v5 changed the path only for single downloads by ID;
  release-vale downloads by name. v8 errors on digest mismatch by default.
- github-script v9 drops `require('@actions/github')`; the scripts here
  use `github.rest.*`, `github.paginate` and relative `require` only.
- changesets/action stays at v1.9.0, already node24. v2 renames every
  input and moves to Changesets v3, which is a migration, not a pin.
The `npm warn Unknown user config "always-auth"` line in issue #294 was
written by actions/setup-node v4.4.0: its authutil appends
`always-auth=<input>` to the .npmrc it creates for `registry-url`, and
npm 11 warns that the option is unknown and the next major refuses it.
setup-node v6.1.0 removed that line (actions/setup-node#1436) and v7.0.0
stopped exporting the dummy NODE_AUTH_TOKEN (actions/setup-node#1558),
so after the pin bump the OIDC exchange is the only credential in each
publish job.

Nothing in the workflows referenced the option, so the previous commit
already fixes it; this one leaves a note beside each publish step so the
pin is not lowered again.
`node-version: 24` was repeated in thirteen setup-node steps across seven
workflows with no single source. The number now lives in .nvmrc and
every step that follows a checkout reads it with `node-version-file`.

The one exception is the Vale publish job, which deliberately checks
nothing out while it holds an OIDC identity, so there is no file for
setup-node to read; it keeps a literal `node-version: 24` with a comment
pointing at .nvmrc.

24 is the choice because it is the Active LTS line today
(nodejs/Release schedule.json: LTS since 2025-10-28, maintenance from
2026-10-20). The Current line, 26, does not become LTS until 2026-10-28,
seven weeks out, and the local toolchain runs 24.14.0. packages/cli's
`engines.node` is a consumer-facing floor and is untouched.
@thecodedrift
thecodedrift force-pushed the fix/npm-always-auth-warning branch from b6f389c to d3b451e Compare September 11, 2026 00:47
@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 4m 35s —— View job


Review: ci: move every action to the node24 runtime and pin by SHA

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contains only the triggering @claude /review comment — no prior reviews, threads, or comments exist on this PR, so this is the first review and covers the whole diff.

  • Read .prior-review.json
  • Fetch PR diff via gh pr diff
  • Review workflow changes for correctness, security, clarity
  • Post findings

What I checked

  • Every uses: in .github/workflows is now a full 40-character commit SHA — verified by grepping all 47 uses: lines across all 13 workflow files and confirming the count matches a pattern requiring a 40-hex-char pin. No floating tags remain anywhere in the repo's workflows.
  • Pins are consistent across files: the same actions/checkout, actions/setup-node, and pnpm/action-setup SHAs are used everywhere they appear, so there's no accidental split where one job got the bump and a sibling didn't.
  • checkout@v7's fork-checkout restriction doesn't hit anything here. Traced the trigger for every workflow that checks out a PR ref: claude-code-review-on-demand.yml uses issue_comment (not pull_request_target/workflow_run), openspec-label.yml uses plain pull_request, and the workflow_run checkouts in release-cli-nightly.yml are gated behind github.event.workflow_run.event == 'push' before any checkout happens. Matches the PR description's claim.
  • github-script@v9's removal of the injected require('@actions/github') doesn't affect stack-breadcrumb.yml: grepped every script block and confirmed they only use github.rest.*, github.paginate, and require() of local .cjs files, none of which need the removed injection.
  • node-version-file: .nvmrc placement: in every job that uses it, actions/checkout runs first, so .nvmrc exists on disk before setup-node reads it. No ordering bug.
  • .nvmrc contents (24) and packages/cli/package.json's engines.node (>=22.22.0) don't conflict.

One caveat on process: I could not independently re-verify the tag→SHA mappings in the PR body's runtime table against the actions' actual repos — gh api repos/.../git/ref/tags/... calls in this sandbox require approval that isn't available in this session, and git isn't usable here at all. I'm relying on the PR description's own verification method (gh api ... /git/ref/tags/<tag>, dereferenced through annotated tags) for that part, and focused my own checking on internal consistency of the diff and on tracing the actual trigger/permission context of each affected job, both of which check out.

Findings

One non-blocking observation, posted inline on .github/workflows/release-vale.yml:344:

  • [New] The publish job's node-version: 24 is, by necessity, the one remaining place the Node major is a literal rather than node-version-file: .nvmrc (that job does no checkout, so there's no file to read). The PR's own comment already explains this. The only gap is that nothing automated keeps that literal in sync with .nvmrc going forward — worth a small script check if you want to close it the same way this PR closed the equivalent duplication everywhere else, but not a defect in this PR as written.

No other correctness, security, or ordering issues found. The workflow-level reasoning in the PR description (runtime table, trigger-context audit, OIDC token behavior across setup-node majors) matches what's actually in the diff and in the surrounding files I read for context.
· branch fix/npm-always-auth-warning

Comment thread .github/workflows/release-vale.yml
@thecodedrift
thecodedrift merged commit ea9cef8 into main Sep 11, 2026
5 checks passed
@thecodedrift
thecodedrift deleted the fix/npm-always-auth-warning branch September 11, 2026 01:05
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.

OIDC scripts throw warning that will be impacted by next major version of npm

1 participant