chore(#648): every workflow action pin moves to a Node 24 major - #652
Conversation
#625 bumped only the job it introduced, leaving ci.yml mixed-version: actions/checkout@v7 in gate-scripts, actions/checkout@v4 in build-and-test and ios-simulator-build. Deprecation annotations are emitted per JOB, not per workflow, so each remaining v4 job raised its own "Node.js 20 is deprecated" warning. All 17 pins across the five workflow files were enumerated rather than just the two the issue named. Changed: actions/checkout v4 -> v7 (six sites), actions/cache v4 -> v6 (four sites), actions/github-script v7 -> v9 (one site). Unchanged: maxim-lobanov/setup-xcode@v1, whose moving v1 tag already resolves to a node24 build, and gate-scripts' own checkout@v7 / setup-python@v7. The target for each was read out of that action's action.yml at the pinned ref rather than inferred from the version number, and one action does not follow the pattern: actions/github-script@v7 is `using: node20` and node24 only arrives at v8. A sweep that made every pin say v7 would have produced a repo that looked consistent while leaving require-issue-labels.yml deprecated. No bump changed an interface: action.yml at the new ref is byte-identical to the old apart from the `using:` line in all three cases, so fetch-depth, submodules, persist-credentials and cache-hit keep their defaults. The two real behaviour changes are inert here: checkout v6 persists credentials to a separate file rather than .git/config (no workflow reads them or pushes), and checkout v7 blocks fork-PR checkout under pull_request_target / workflow_run (neither trigger is used anywhere). Closes #648 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ust checkout The annotation GitHub emitted on the base commit lists every Node 20 action in the job: "actions/cache@v4, actions/checkout@v4". Bumping only the action the issue names would have left the warning standing while the diff looked like a fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Per-job annotation evidence, before and afterAnnotations are emitted per job, so this is read per job off real runs rather than per workflow.
The Node 20 deprecation warning is gone from both jobs that were bumped, and The annotation named
|
| # | step | base (@v4) |
PR (@v7/@v6) |
|---|---|---|---|
| 1 | Set up job | success | success |
| 2 | actions/checkout |
success | success |
| 3 | Select Xcode | success | success |
| 4 | Cache SwiftPM artifacts | success | success |
| 5 | swift build |
success | success |
| 6 | swift test |
failure | failure |
| 12 | Post actions/checkout |
success | success |
Identical, including the failing step. Failure count is 56 recorded issues on both, and the
signature is #585's: Issue570HealingApproxTests failing on (fitted.uDegree → 1) > 1 and
deviation → 23.9999..., which is exactly the degree-1 collapse kernel patch 0019 (#522) fixes and
the pinned kernel does not carry.
ios-simulator-build is green and step-identical to base, including its post steps — Post Cache SwiftPM artifacts (that is actions/cache@v6's save half, skipped on the red job but exercised
here) and Post Run actions/checkout@v7 (the credential cleanup that v6 changed). Those are the two
places a bad cache or checkout bump would surface, and both pass.
What could not be verified in CI, and why
Three workflows are not reachable from a pull request, so their pins are verified by
action.yml runtime + tag existence + the same actions passing in ci.yml, not by a run of their
own:
require-issue-labels.yml(actions/github-script@v9) triggers onissuesevents. Workflows
triggered by non-PR events always run the default branch's copy of the file, so this job cannot
execute this branch's version until the change reachesmain— not merely until this PR merges to
refactor/381-pass1b. This is the one changed pin no run in this PR exercises. Its two v9 breaking
changes were checked against the script by grep (require(,getOctokit,@actions/github: no
matches); if v9 ever misbehaves,v8is the zero-code-change fallback that is equallynode24.kernel-integration.ymlruns only whenScripts/patches/**orScripts/build-occt.shchanges,
which this PR does not touch. Itsactions/cache@v6usage additionally reads
steps.occt-cache.outputs.cache-hit;cache-hitis still a declared output at v6 with a
byte-identical declaration, andci.ymlexercises the same action at the same ref.occt-parallel-crash-test.ymlisworkflow_dispatch-only and builds OCCT from source on
Windows and macOS. Not dispatched: it is a 60-minute two-platform source build, and its only change
isactions/checkout@v4→@v7, the identical pin proven green twice inci.ymlabove.
release.yml likewise only runs on release publish; its change is the same checkout pin.
The byte-identical action.yml establishes that fetch-depth, submodules, persist-credentials and cache-hit keep their DECLARED defaults. Semantics live in the compiled JS, and the very next sentence names two real behaviour changes, so the clause claimed more than its own evidence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #648
#625 bumped only the job it introduced, so
ci.ymlwas left mixed-version:actions/checkout@v7ingate-scripts,actions/checkout@v4inbuild-and-testandios-simulator-build. Deprecationannotations are emitted per job, not per workflow, so each remaining v4 job raised its own
"Node.js 20 is deprecated" warning.
Full pin inventory — all 17, across all five workflow files
The issue named
ci.yml. Sweeping only that file would have left the repo mixed-version while makingone file look consistent, which is the same defect one directory up. Every
uses:in.github/workflows/is listed, whether or not it changed.The runtime column is what that action's own
action.ymldeclares inruns.using:at the pinnedref, read via
gh api repos/<owner>/<action>/contents/action.yml?ref=<tag>, not inferred from theversion number.
ci.ymlgate-scriptsactions/checkoutv7v7node24ci.ymlgate-scriptsactions/setup-pythonv7v7node24ci.ymlbuild-and-testactions/checkoutv4v7node20→node24ci.ymlbuild-and-testmaxim-lobanov/setup-xcodev1v1node24ci.ymlbuild-and-testactions/cachev4v6node20→node24ci.ymlios-simulator-buildactions/checkoutv4v7node20→node24ci.ymlios-simulator-buildmaxim-lobanov/setup-xcodev1v1node24ci.ymlios-simulator-buildactions/cachev4v6node20→node24kernel-integration.ymlbuild-and-testactions/checkoutv4v7node20→node24kernel-integration.ymlbuild-and-testmaxim-lobanov/setup-xcodev1v1node24kernel-integration.ymlbuild-and-testactions/cache(OCCT.xcframework)v4v6node20→node24kernel-integration.ymlbuild-and-testactions/cache(SwiftPM)v4v6node20→node24occt-parallel-crash-test.ymltest-windowsactions/checkoutv4v7node20→node24occt-parallel-crash-test.ymltest-macosactions/checkoutv4v7node20→node24release.ymlverify-binary-pinsactions/checkoutv4v7node20→node24release.ymlverify-binary-pinsmaxim-lobanov/setup-xcodev1v1node24require-issue-labels.ymlcheck-labelsactions/github-scriptv7v9node20→node2411 changed, 6 left alone. After the sweep, no pin in the repo resolves to a Node 20 runtime.
The two deliberately left unchanged, with reasons
maxim-lobanov/setup-xcode@v1(4 sites). The issue flagged this as third-party and possiblywithout a Node 24 major. It has one, in place.
v1is the only major tag that exists, and themoving tag resolves to commit
ed7a3b1(2026-03-18) — the same commitv1.7.0resolves to —whose
action.ymldeclaresusing: 'node24'. Bumping was neither possible nor needed, and the pinis not stale.
gate-scripts' ownactions/checkout@v7andactions/setup-python@v7— alreadynode24fromNeither new gate script is invoked by CI, a hook, or any script #625, and v7 is the current major for both.
actions/setup-pythonwas checked rather than assumedcorrect, because it shares a version number with the action that breaks the pattern below.
Bumping
actions/checkoutalone would have left the warning standingThe issue, and the annotation quoted in it, are both phrased around
actions/checkout@v4. The actualannotation GitHub emitted on the base commit names two actions:
One annotation per job, listing every Node 20 action in that job. So bumping only
actions/checkout—the action the issue names — would have left
actions/cache@v4behind, the warning would still havebeen emitted on both
build-and-testandios-simulator-build, and the diff would have looked like afix while changing nothing observable. That is the same trap as #647's, one level in: #647 got the
per-job granularity right and missed the sibling jobs; reading only the issue's headline action
would have got the per-job granularity right and missed the sibling action in the same job.
This is why the fix is 11 pins and not 6.
The version number is not the runtime
actions/github-script@v7isusing: node20, despite matching thev7thatactions/checkoutand
actions/setup-pythonare correct at. node24 first arrives in github-script v8. A sweepdriven by "make everything say v7" — which the issue's own summary invites, naming v7 as the current
major — would have produced a repo where every pin agreed and
require-issue-labels.ymlwas stilldeprecated. That job goes to v9, the current major.
Each target tag was also confirmed to exist before pinning
(
gh api repos/<owner>/<action>/git/matching-refs/tags/v): checkoutv1–v7, cachev1–v6(no v7 — v6 is the current major, which is why cache lands on a different number than checkout),
github-script
v1–v9, setup-pythonv1–v7, setup-xcodev1only.Breaking-change check, per action
No bump changed an interface.
action.ymlat the new ref is byte-identical to the old ref apartfrom the
using:line, in all three cases — established by diffing the two files, not by trustingrelease notes:
The same single-line diff holds for
cachev4→v6 andgithub-scriptv7→v9. Sofetch-depth(default
1),submodules(defaultfalse),persist-credentials(defaulttrue) and cache'scache-hitoutput all keep their declared defaults. What the manifest proves is the declaredinterface; the compiled JS behind it is covered by the behaviour-change review below, not by this
diff.
kernel-integration.ymlreadssteps.occt-cache.outputs.cache-hitto skip a 60-minute source build; that output is unchanged.Behaviour changes that exist outside the interface, from each action's changelog:
actions/checkoutv6.0.0 — "Persist creds to a separate file" (PR 2286).Credentials move out of
.git/configinto a separate credential file. Inert here:grep -rn "persist-credentials\|GITHUB_TOKEN\|git push\|git config\|\.git/config\|extraheader" .github/workflows/returns nothing — no workflow pushes, reads the persisted token, or re-uses the checkout's git
credentials.
actions/checkoutv7.0.0 — "Block checking out fork PR forpull_request_targetandworkflow_run" (PR 2454). A genuine refusal,not a warning. Inert here: neither trigger is used anywhere in the repo. The triggers in use are
pull_request(ci, kernel-integration),push,workflow_dispatch,releaseandissues, andplain
pull_requestis unaffected.actions/checkoutv5 /actions/cachev5 /actions/github-scriptv8 — minimum runnerv2.327.1. Everyruns-onin the repo is GitHub-hosted (ubuntu-latest,macos-15,macos-latest,windows-latest); there are no self-hosted runners, so the floor is met.actions/cachev6.0.0 — ESM migration. Packaging only; no documented behaviour change and nointerface change per the diff above.
actions/github-scriptv9.0.0 — two breaking changes.require('@actions/github')no longerworks inside a script, and
getOctokitis now an injected parameter, so a script declaringconst getOctokitgets aSyntaxError. Neither applies: the script inrequire-issue-labels.ymluses only the injected
contextandgithub.rest.issues.*, and greps clean forrequire(,getOctokitand@actions/github.Nothing needed adapting, and nothing had to be held back at an old pin for a behavioural reason.
Evidence
gate-scriptsgates run clean locally on this branch:check-bridge-index.pyexit 0(728 symbols / 383 classes, 0 stale, 0 misfiled),
check-null-handle-guards.pyexit 0,check-docs-defaults.pyexit 0 (0 drifted, 0 unverified),count-operations.pyexit 0(4301 = README = API_REFERENCE).
are emitted per job and can only be read off a real run.
build-and-test(macOS) is red branch-wide onrefactor/**from #585's pinned-kernel mismatch andstays red here for that reason. What this PR is accountable for is that it still reaches
swift testrather than failing earlier, at checkout or cache restore.
Docs
docs/CHANGELOG.mdgains an Unreleased entry under the Pass 1b heading. No version invented, nooperation counts touched.
🤖 Generated with Claude Code