Skip to content

ci: move every first-party action off the Node 20 runtime - #328

Open
FrameAutomata wants to merge 3 commits into
mainfrom
ci/326-node24-actions
Open

ci: move every first-party action off the Node 20 runtime#328
FrameAutomata wants to merge 3 commits into
mainfrom
ci/326-node24-actions

Conversation

@FrameAutomata

@FrameAutomata FrameAutomata commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Part 1 of #326. Bumps the 63 actions/* pins across all 16 workflows. Part 2 (ci/326-node24-third-party) handles the six third-party actions on the release workflows and is stacked on this branch.

GitHub currently reports this as a run annotation:

Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24

The forcing is the step before removal, and when that lands every workflow breaks at oncerelease-traceway.yml and release-cli.yml included. Losing the ability to cut a release is a bad way to find out.

Action Uses
actions/checkout v4 → v7 25
actions/setup-go v5 → v7 15
actions/upload-artifact v4 → v7 10
actions/download-artifact v4 → v8 5
actions/setup-node v4 → v7 4
actions/cache v4 → v6 4

runs.using was read at each source and target ref: all six were node20, all six are now node24.

The three jumps with real behaviour changes

Not a sed 's/@v4/@v7/' — these skip 2–3 majors. Each was checked against how this repo actually calls it.

setup-go v6 changed toolchain handling (#460), and this repo cares: CLAUDE.md documents keeping go and toolchain in go.mod distinct. v6 installs the toolchain version rather than the go line's, and sets GOTOOLCHAIN=local so Go does not download one itself.

Both modules pin go 1.26.2 / toolchain go1.26.6, so the same compiler runs either way — v5 got there by downloading 1.26.6 on first use, v7 installs it directly. Same version, one less download.

GOTOOLCHAIN=local does mean a job that installs an older Go than a module's floor now errors instead of silently upgrading. One job installs an older Go: benchmark-hardware.yml (go-version: '1.25'). It only builds benchmarks/loadgen, which declares go 1.25; the backend there is built by docker compose --build on the Hetzner SUT, not by the runner's Go. So nothing regresses.

backend-vulncheck.yml documented the v5 mechanism in a comment — rewritten in this PR, since it would otherwise now be false.

setup-node v5 auto-enables caching when package.json has a packageManager field. No package.json in this repo has one, and three of the four call sites already set cache: "npm" explicitly.

download-artifact v5 changed output paths for single downloads by ID. Nothing here downloads by ID — every call site uses name:, or pattern: + merge-multiple: true, or downloads everything. v8 additionally errors rather than warns on a digest mismatch, which is the behaviour you'd want on a release artifact anyway.

upload-artifact v7's archive: false is opt-in with the default unchanged, and cache v5/v6 are runtime + ESM only — the key/restore semantics I'd expected to change didn't.

Runner requirement

These releases require Actions Runner ≥ 2.327.1. Every job here is ubuntu-latest or ubuntu-24.04 — all GitHub-hosted, no self-hosted runners — so that's already met.

Validating

backend.yml, backend-vulncheck.yml, cli.yml, cli-lint.yml and cli-contract.yml all run on the ci label, which exercises checkout, setup-go and cache — including the setup-go toolchain change, which is the one worth actually watching. The benchmark workflows (upload-artifact/download-artifact) are workflow_dispatch-only and cost real money to run, so those two are argued from the changelogs and the call sites above rather than from a green run.

Note for #315

frontend.yml, incoming via #315, adds actions/checkout@v4 and actions/setup-node@v4. Worth bumping those to @v7 there so it doesn't land already stale — this PR can't reach that file.

🤖 Generated with Claude Code

Also: one docs recipe

docs/pages/client/js-sdk/sourcemap-upload.mdx ships a copy-paste GitHub Actions recipe pinning checkout@v4 and setup-node@v4 — a reader following it lands on the same annotation. Bumped to @v7 in a separate commit.

Left alone deliberately: the node-version: 20 on the next line of that snippet (a question about what Node the JS SDK supports, not about the action runtime), and website/content/blog/auto-fix-loop.mdx, which pins upload-artifact@v4 in a published, dated post.

From a cleanup pass over this diff

backend.yml's Go caches would have carried a dead toolchain forever. Those three jobs set cache: false on setup-go and hand-roll actions/cache over ~/.cache/go-build and ~/go/pkg/mod, keyed on go.sum with a ${{ github.run_id }} suffix — so every run saves a fresh entry and the restore-keys carry the previous tree forward.

Neither cached tree survives a Go version change. go-build entries are compiler-keyed, and until this branch GOTOOLCHAIN=auto materialised a ~250MB golang.org/toolchain@...go1.26.6 module inside GOMODCACHE. setup-go v7 never writes that module again — but nothing deletes it either, and the module cache has no trimming, so the run_id rotation would have copied a dead toolchain forward on every run indefinitely.

Fixed by putting the resolved Go version in the key (steps.setup-go.outputs.go-version, with id: setup-go added to the three steps) rather than by a cleanup step. That fixes it for every future Go bump instead of this one — the cache generation now rotates whenever the compiler does. Costs one cold run per job, most of which was dead weight anyway.

The vulncheck comment rewrite got tightened. The first version named GOTOOLCHAIN=local for both setup-go and the golang Docker bases while asserting the two behave differently, which destroyed the contrast the comment exists to draw — the real difference is which Go gets installed first. The setup-go half no longer mentions the variable, and the Docker half now says explicitly that it ignores the toolchain line. The parenthetical recording v5's old behaviour is gone: no file pins setup-go@v5 any more, and git blame is the right home for a version delta.

Follow-ups filed

GitHub is force-migrating node20 actions onto Node 24, which currently
surfaces as a run annotation. The failure mode when the shim is removed
is that every workflow breaks at once, release-traceway.yml and
release-cli.yml included, so this moves ahead of it rather than
discovering it at release time.

All 63 actions/* pins across the 16 workflows:

  checkout          v4 -> v7   (25 uses)
  setup-go          v5 -> v7   (15 uses)
  upload-artifact   v4 -> v7   (10 uses)
  download-artifact v4 -> v8   ( 5 uses)
  setup-node        v4 -> v7   ( 4 uses)
  cache             v4 -> v6   ( 4 uses)

runs.using was read at each source and target ref: every one of these
was node20 and is now node24. The six remaining node20 actions are all
third-party and all on release workflows (docker/*, wrangler-action,
setup-helm); they are a separate change because no PR label can exercise
them.

The three multi-major jumps with real behaviour changes, and why each is
inert here:

setup-go v6 stops relying on Go's toolchain download. It installs the
`toolchain` directive's version rather than the `go` line's, and sets
GOTOOLCHAIN=local so no download happens. Every module built in CI
already pins toolchain go1.26.6, so the same compiler runs, one download
sooner. The one job that installs an older Go than the repo floor
(benchmark-hardware, go-version 1.25) only builds benchmarks/loadgen,
which declares go 1.25 -- it does not build the backend, whose image is
built by docker on the SUT. backend-vulncheck.yml documented the v5
mechanism in a comment, so that comment is rewritten here.

setup-node v5 auto-enables caching when package.json carries a
`packageManager` field. No package.json in the repo has one, and three
of the four call sites already set `cache: npm` explicitly.

download-artifact v5 changed the output path for single downloads by ID.
Nothing here downloads by ID -- every call site uses `name:`, or
`pattern:` with `merge-multiple: true`, or downloads all artifacts. v8
additionally errors rather than warns on a digest mismatch, which is the
behaviour we would want anyway.

All runners are GitHub-hosted (ubuntu-latest / ubuntu-24.04), so the
2.327.1 minimum runner version these releases require is already met.

Refs #326.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The snippet is a copy-paste CI recipe, so a reader who follows it lands
on the same deprecation annotation this branch just cleared out of our
own workflows.

Only the two action pins are touched. The `node-version: 20` on the next
line is stale too, but that is a question about what Node the JS SDK
supports rather than about the action runtime, so it wants its own
change.

Refs #326.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two follow-ups from reviewing the action bumps.

The vulncheck comment named GOTOOLCHAIN=local for both setup-go and the
golang Docker bases while asserting the two behave differently, which
destroyed the contrast the comment exists to draw. The real difference is
which Go gets installed first, so the setup-go half no longer mentions the
variable and the Docker half says explicitly that it ignores the
`toolchain` line. The parenthetical recording what v5 used to do is gone
too -- no file pins setup-go@v5 any more, and git blame is the right home
for a version delta.

backend.yml pins `cache: false` on setup-go and hand-rolls actions/cache
over ~/.cache/go-build and ~/go/pkg/mod, keyed on go.sum with a run_id
suffix so each run saves a fresh entry and the restore-keys carry the
previous tree forward. Neither cached tree is valid across a Go version
change: go-build entries are compiler-keyed, and until this branch
GOTOOLCHAIN=auto materialised a ~250MB toolchain module inside
GOMODCACHE. setup-go v7 installs the toolchain directly and never writes
that module again, but nothing deletes it either -- the module cache has
no trimming, so the run_id rotation would have copied a dead toolchain
forward on every run indefinitely.

Adding the version to the key fixes that without a cleanup step, and
fixes it for every future Go bump rather than this one: the cache
generation now rotates whenever the compiler does. Costs one cold run per
job, most of which was dead weight anyway.

Refs #326.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FrameAutomata added a commit that referenced this pull request Aug 28, 2026
Nothing in this repo watched action versions: no dependabot.yml, no
renovate.json, and the strings appear nowhere. That is how the Node 20
runtime migration (#326) was found twice in one day, both times because a
human read a run annotation. #328/#329 bought distance from that deadline
but no detection, so the next major lands exactly as silently.

Adopt Dependabot for the github-actions ecosystem only. npm and Go modules
are left unwatched on purpose -- backend/ and cli/ already run govulncheck
daily, and those ecosystems would bury the signal this file exists for.

Two groups, split the same way #328/#329 split by hand, on what a major
means rather than on who ships it: actions/* majors are normally runtime
bumps, wide and mechanical and reviewable as one sweep, while a third-party
major can change behaviour -- wrangler-action v4 silently moved its default
Wrangler CLI from 3 to 4, which is the whole of #331.

The PRs are deliberately not auto-labelled `ci`. That label is the
collaborator trust gate that makes PR CI opt-in, and an unattended bot PR
proposing an action version nobody has read should not trip it; a reviewer
applies it after looking, as for any human PR. Worth being clear-eyed that
this is mostly detection and not validation: `ci` reaches only backend*.yml
and cli*.yml, while most pins sit on release-*.yml, benchmark-*.yml and
traceway-autofix.yml, where validating a bump still means a
workflow_dispatch dry run. Detection is the half that was missing.

Settle the two Node questions in the same pass, since they are the same
discipline. release-docs.yml and release-website.yml pinned
`node-version: "latest"`, which floats two production deploys onto whatever
Node shipped that morning, up to and including a non-LTS Current release --
and neither docs/package.json nor website/package.json declares an engines
floor to catch it. Both now pin 22, the major already used by
release-traceway.yml, benchmark-processor.yml and the node:22-alpine
Dockerfiles, matching frontend/package.json's >=22. Whether Traceway moves
to Node 24 is now one decision made once rather than one made silently on
two deploy paths.

The source-map upload recipe was the last surviving `node-version: 20` in
the repo, recommending to readers a major this repo uses nowhere while
#328 had just bumped the action pins around it to the Node 24 majors. It
recommends 22 now, the same version the shipped Dockerfiles run.

Closes #330

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant