Skip to content

ci: three actions still target Node.js 20, and sixteen SHA-pinned actions cannot be checked at all #15332

Description

@mrveiss

What

Every CI job currently emits this annotation:

Node.js 20 is deprecated. The following actions target Node.js 20 but are being
forced to run on Node.js 24: actions/cache@v4.

"Forced to run on Node.js 24" is a compatibility shim GitHub applies today and will remove. When it goes, those steps stop working — and the timing is not ours to choose, which is the argument for fixing it before it becomes an outage rather than after.

Measured

The repository is mostly current — actions/checkout@v7, actions/cache@v6, actions/setup-python@v7, actions/upload-artifact@v7. Three call sites are not:

Action Location Repo standard elsewhere
actions/cache@v4 .github/actions/setup-python-suite/action.yml:155 @v6
actions/cache@v4 .github/actions/setup-python-ci/action.yml:176 @v6
actions/checkout@v4 .github/workflows/auto-merge-base-into-parked-branches.yml:79 @v7

The two cache@v4 sites are the ones producing the annotation, and they sit inside composite actions — which is why they were missed by whatever bumped the workflows: a sweep over .github/workflows/*.yml does not reach .github/actions/*/action.yml.

The checkout@v4 is mine, introduced in #15316. I wrote it against the older example rather than checking what the repo standardised on, and no check caught the divergence.

The part worth a decision, not just a bump

Sixteen actions are SHA-pinned:

actions/checkout@9c091bb…      actions/setup-node@82076278…
actions/setup-python@5fda3b95…  actions/upload-artifact@043fb46d…
github/codeql-action/*@db488dd…  docker/build-push-action@53b7df96…
…and ten more

SHA-pinning is correct for supply-chain integrity — it is what stops a moved tag executing new code. But it also means these never pick up a runtime bump, and their Node version cannot be read from the ref. So the annotation above names only the version-tagged stragglers; any SHA-pinned action still targeting Node 20 is invisible to it and will break with equally little warning.

That tension is the real content of this issue. Un-pinning to fix a runtime deprecation would trade a supply-chain control for convenience, which is the wrong direction. The workable answer is to resolve each pinned SHA to its release and record the Node runtime alongside the pin, so the next deprecation is a lookup rather than an archaeology exercise.

Acceptance criteria

  • The three version-tagged call sites move to the versions the rest of the repository already uses
  • Composite actions under .github/actions/ are included in whatever sweep keeps action versions current — the gap that let two of these survive
  • Each SHA-pinned action carries a comment naming the release it pins and that release's Node runtime; pins are not removed
  • A check fails when a workflow or composite action uses an action version older than one already used elsewhere in the repository, so a new file cannot reintroduce an old version the way ci(branches): merge base into parked PR-less branches on a schedule (#15306) #15316 did
  • Verified from a real run's annotations that the Node 20 warning is gone, not from the files

Note on the last criterion

The annotation is the only signal that surfaced this, and it appears in job annotations rather than in any check's pass/fail. Nothing fails today, which is precisely why three call sites drifted. Whatever check comes out of this should be the thing that fails — an annotation nobody is required to read is how this reached three sites and sixteen unreadable pins.

Activity

  1. mrveiss commented on Aug 31, 2026

    @mrveiss
    OwnerAuthor

    Partially landed — staying open on one criterion. 79813b1533 (PR #15347), verified against merged Dev_new_gui.

    Met: all three Node-20 pins bumped to the version already standard elsewhere in the repo — setup-python-ci/action.yml:176 and setup-python-suite/action.yml to actions/cache@v6, auto-merge-base-into-parked-branches.yml:79 to actions/checkout@v7.

    The issue's second premise was already stale. It says sixteen SHA-pinned actions "cannot be checked at all"; in fact all 44 SHA-pinned call sites already carry a # vX.Y.Z comment. So rather than build the checking mechanism the issue asked for, repo_tests/workflow_action_version_regression_test.py now enforces the invariant that already holds — every SHA pin must keep a readable version comment, and no tag-pinned action may be older than the repo's own maximum for that exact action string.

    NOT met, deliberately: cross-checking SHA-pinned versions against each other. docker/login-action, docker/build-push-action and sigstore/cosign-installer are pinned to genuinely different releases in different workflows today. Enforcing consistency would break CI immediately and requires an owner decision on which version is canonical for each — so it is recorded as an explicit scope exclusion in the guard's docstring rather than silently dropped. That is the one thing keeping this issue open.

    On the actions/cache@v6 risk, since it was raised and could not be settled from source: that bump is gated on runner.environment == 'self-hosted', self-hosted runner binaries do not auto-update, and a Node-runtime mismatch fails a job outright rather than warning. Nothing in the repo tracks the runner version. Settled empirically instead — code-quality completed success on the bumped head, and that job is runs-on: [self-hosted, Linux, X64] using the very action file the bump lives in. Observed, not inferred.

    One guard improvement worth recording: the version-comparison test was unfalsifiable as first written — with zero tag drift in the repo, version < best is never true and an inverted operator would have passed identically. It is now pinned by planted fixtures and proven by mutation (inverting the operator fails it; restoring passes).

  2. modified the milestones: Backlog, v0.14.0 on Sep 12, 2026
  3. mrveiss commented on Sep 20, 2026

    @mrveiss
    OwnerAuthor

    Branch-freeze re-verification against current origin/main (2026-09-20). Tracked branch issue-15332-ci-actions is already gone from origin.

    AC1-2, 4 met; AC3 met via a documented, reasoned substitute; AC5 spot-checked live:

    • All 3 version-tagged call sites now match the repo standard: .github/actions/setup-python-suite/action.yml:225 and setup-python-ci/action.yml:195 both actions/cache@v6; auto-merge-base-into-parked-branches.yml:79 is actions/checkout@v7.
    • Composite actions are in scope — repo_tests/workflow_action_version_regression_test.py's own docstring says so explicitly: "a sweep over .github/workflows/*.yml alone would have missed them, which is why this guard walks both directories."
    • / substitute: AC3 as literally written ("Node runtime alongside the pin") was not implemented — a documented alternative was, in its place. The guard's docstring states this directly: every SHA pin now carries a readable # vX.Y.Z comment (spot-checked: actions/checkout@9c091bb... # v7.0.0, github/codeql-action/*@b96794f... # v4.38.0), plus a cross-consistency check that every pin of the same SHA carries agreeing version labels — which caught and is regression-tested against a real prior bug (codeql-action labelled # v3 in one workflow and # v4 in another, same SHA). The reasoning given for not resolving to Node runtime: that needs network access a repo_tests guard doesn't have. Judgment call, not an oversight — flagging it as a deviation from the literal AC rather than silently counting it as done.
    • test_no_tag_pinned_action_is_older_than_the_repo_standard is the required fail-on-regression check, with a negative control (test_the_comparison_flags_a_planted_regression) proving it actually catches a planted regression rather than just reading clean today. It runs as part of repo_tests/ under python-suite, which is now a required context (independently confirmed while triaging ci: the backend Python suite gates nothing — python-suite is not a required check #14353 in this same pass) — so this does gate merges, just via the existing required suite rather than a dedicated new check.
    • Spot-checked a real completed run (ci.yml run 35525014904, python-suite shard job): zero annotations of any kind, zero Node 20 mentions. Not exhaustive across every job type, but a real run, not a file read.

    4 of 5 fully met, 1 deliberately substituted with reasoning recorded. Leaving open for a decision on whether the AC3 substitute is accepted as closing that criterion, since I don't think that call is mine to make.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions