Skip to content

chore(ci): upgrade GitHub Actions to Node 24 runtime majors (#36850) - #37261

Open
nicobytes wants to merge 15 commits into
mainfrom
nicobytes/issue-36850-node-24-actions-sweep
Open

chore(ci): upgrade GitHub Actions to Node 24 runtime majors (#36850)#37261
nicobytes wants to merge 15 commits into
mainfrom
nicobytes/issue-36850-node-24-actions-sweep

Conversation

@nicobytes

@nicobytes nicobytes commented Aug 28, 2026

Copy link
Copy Markdown
Member

Upgrades every GitHub Action under .github/ to a major whose runs.using is node24, so the
runner's Node 20 deprecation annotations disappear and the pipeline survives the removal of the
compatibility shim.

Spec: #37193 (merged). Parent: #36850. Deferred scope: #37194.

Scope is .github/ only — no Java, no Angular, no DB, no ES mapping, no API contract. Nothing
falls under ROLLBACK_UNSAFE_CATEGORIES.md.


AC-001 is satisfied, and here is the evidence

The acceptance criterion is about annotations, not a green check — a run can be green and still
carry them. Scanning both runs with the same gh api query:

Job Before (run 33122809569) This PR (run 33191856953)
Initialize / Check Changed Files 1 annotation 0
Label PR / Apply Area Labels 1 0
PR Test / Setup Test Matrix 1 0
Finalize / Prepare Report Data 1 0
PR Build / Initial Artifact Build (the job #36850 cites) pass, 13m20s

The scan finds 4 annotated jobs on the baseline and 0 here, so this is not a false green — the query
demonstrably detects annotations when they exist.

Note Initialize / Check Changed Files. Those annotations came from dorny/paths-filter and
dawidd6/action-download-artifact, neither of which #36850 lists. The runner emits its annotation
per job, so had this PR bumped only the six actions the issue names, that job would still be
annotated and AC-001 would fail on its own terms. The four extra third-party bumps were necessary,
not scope creep.

What changed

Action From To
actions/checkout v4 ×54, v3 ×2, v2 ×1, SHA ×5 v7.0.1
actions/download-artifact v4 ×13, SHA ×1 v8.0.1
actions/cache + /restore + /save v4 ×25, v3 ×1 v6.1.0
actions/upload-artifact v4 ×11 v7.0.1
actions/setup-node v4 ×6, v2-beta ×1, SHA ×1 v7.0.0
pnpm/action-setup v4 ×2 v6.0.10
actions/github-script v7 ×14 v8
docker/login-action v3 / v3.0.0 ×12 v4.6.0
dawidd6/action-download-artifact v6 ×3 v24
dorny/paths-filter v3.0.1 v4.0.3

Plus 8 stale references across 7 README.md files. Each site keeps its pinning style — float tag
stays a float tag, SHA pin gets the new SHA with a corrected # vX.Y.Z comment. Two SHA pins
disagreed on main (the same checkout SHA labelled # v4.2.2 in one file and # v4.2.0 in
others); both are now correct.

11 commits, each independently revertable. main allows only merge, not squash, so every one lands
in history on its own — the messages carry the per-batch reasoning.

Three places where a major carried real behaviour

download-artifact v8 changed digest-mismatch from warn to error — the only change here that
can turn a green build red. Exposure is the multi-GB maven-repo artifact and finalize-phase's
build-reports-* pattern (N artifacts = N chances, on every PR and merge-queue run). Landed in two
commits: first v8 with an explicit digest-mismatch: warn, making the runtime bump provably
behaviour-neutral, then a separate commit flipping to error. If a mismatch appears, revert that
one commit
— the Node 24 migration stays landed.

checkout v7's fork-PR block cannot fire in this repo. Read src/input-helper.ts and
src/unsafe-pr-checkout-helper.ts at tag v7.0.1: the guard is only reached when a custom
repository: or explicit ref: is supplied, and the throw additionally needs a genuine fork head
and inputs pointing at the fork's code. cicd_post-workflow-reporting.yml:58 is a bare uses:
with no with: block at all; cicd_publish-pr-test-image.yml has no checkout step and is gated to
non-forks. So no v6 pins, and allow-unsafe-pr-checkout: true is deliberately not used — it
would permanently disarm a real pwn-request protection against a risk this repo does not have.

setup-node: package-manager-cache: false is deliberately NOT added, deviating from an AC
written in #36850. Both of setup-node's caching paths are already closed: no call site sets cache:
or cache-dependency-path (explicit path), and the repo has no root package.json — only a stub
root package-lock.json — so v5's automatic path cannot fire either, and v6 narrowed it to npm
regardless. Adding the input would be verifiably dead config. Documented as AC-005 in the spec.

Two more decisions worth a glance

github-script stops at v8, not v9. v8 is a pure node24 bump (no "type": "module", still
@actions/github ^6.0.0). Worth recording because the opposite is widely repeated: v9 does not
break require()
src/main.ts still injects wrapRequire, verified at tag v9.0.0. What v9
actually changes is Octokit v5 → v7 under 14 inline scripts, which is a different review. Deferred to
#37194 with that scope, not with a wrong reason.

dorny/paths-filter is isolated in its own commit because it is the mechanism accepted
ADR-0013
depends on. v4.0.0's only change is the node24 runtime — no input, output or filter-semantics change —
and id: filter plus the step's if: guard are untouched, because the fail-open design hangs off
them: a filter that fails to resolve runs more tests, not fewer.

About the red Semgrep check

semgrep-cloud-platform/scan fails on github-actions-mutable-action-tag, and it is
pre-existing, not introduced here
:

uses: refs SHA-pinned mutable tags
main 211 12 199
this branch 211 12 199

Identical. @v4@v7.0.1 swaps one mutable tag for another. Semgrep's scan is diff-aware, so a
changed line is reported as a new finding; other PRs pass only because their diffs do not touch these
lines. main's ruleset has no required status checks, so this does not block.

Going SHA-only is a legitimate change — the repo's own docs ask for pinning to specific versions,
which Semgrep reads more strictly — but it is a convention migration across 199 references and
belongs with #37194's dependabot work, not inside a runtime bump.

Cannot be verified before merge — please review with this in mind

  • cicd_publish-pr-test-image.ymlpull_request_target reads its workflow definition from the
    base branch, so a branch edit is inert until merge. Nothing here can exercise it.
  • cicd_post-workflow-reporting.yml — needs a real completed workflow_run; its fork path only
    surfaces on the first external-contributor PR after merge.
  • Release / LTS / nightly (cicd_6-release.yml, cicd_5-lts.yml, cicd_4-nightly.yml,
    cicd_release-cli.yml, cicd_release-sdk.yml, cicd_comp_release-*, legacy-release_*) — tag and
    schedule gated. These need eyes from someone who knows the release process; no automation covers
    them.
  • publish_docs.yml, legacy-release_sbom-generator.yamldisabled_manually, 0 runs/90d. Their
    pins are bumped for consistency; Upgrade remaining GitHub Actions to Node 24 and add drift controls (follow-up to #36850) #37194 should decide whether to delete them.
  • digest-mismatch: error — one green build is not evidence for a hash check. Watch two
    consecutive PR builds plus a merge-queue run.

Post-merge watch, 72 hours, no release inside it: first cicd_2-merge-queue.yml, first
cicd_3-trunk.yml, first cicd_4-nightly.yml, and the first fork PR.

Deferred to #37194

This PR stops the annotations. It does not stop them coming back — that is #37194, and it matters
more than it looks:

  • 4 first-party actions still on runs.using: 'node16' (issue-fetcher, issue-labeler,
    changelog-report, rc-changelog). They need dist/ rebuilds, and a regenerated ncc bundle
    cannot be diff-reviewed the way YAML can. node16 is further along the deprecation path than the
    node20 that prompted this issue.
  • Cold-path third-party actions. Two are not mechanical: aws-actions/configure-aws-credentials
    v1 → v6 needs OIDC and org-level trust-policy work, and slackapi/slack-github-action v1 → v2+
    changes the payload format.
  • .github/dependabot.yml for the github-actions ecosystem, and extending
    .github/filters.yaml
    so .github/** gets real validation. Today its backend filter matches
    only cicd_comp_*.yml, cicd_1-pr.yml and core-cicd/**/action.yml, so most of the files this PR
    touches get no build, no test and no lint on a PR. That gap is why the deprecation went unnoticed
    until the runner started shouting.
  • A drift guard. An earlier revision of this PR shipped one (a version-floor checker plus a lint
    job). It was removed to keep this PR a pure runtime bump, so nothing currently prevents recurrence —
    see AC-007 in the spec for that trade, recorded rather than glossed over.

🤖 Generated with Claude Code

This PR fixes: #36850

@semgrep-dotcms

Copy link
Copy Markdown
Contributor

Semgrep found 146 github-actions-mutable-action-tag findings:

GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.

If this is a critical or high severity finding, please also link this issue in the #security channel in Slack.

@github-actions github-actions Bot added Area : CI/CD PR changes GitHub Actions/workflows Area : Documentation PR changes documentation files labels Aug 28, 2026
@nicobytes
nicobytes force-pushed the nicobytes/issue-36850-node-24-actions-sweep branch from 7a0c4c7 to 1a3e71f Compare August 28, 2026 10:49
@nicobytes
nicobytes marked this pull request as ready for review August 28, 2026 11:08
@nicobytes
nicobytes requested a review from a team as a code owner August 28, 2026 11:08
Copilot AI lite review requested due to automatic review settings August 28, 2026 11:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Upgrades GitHub Actions workflow and composite-action uses: pins across .github/** to Node 24–compatible major versions, and adds a dedicated PR lint workflow plus a guard script/test suite to prevent action-version drift from recurring (issue #36850).

Changes:

  • Added an action-version drift guard (.github/scripts/check-action-versions.sh) with a bash fixture-based test suite and a new PR-scoped “Actions Lint” workflow.
  • Swept .github/workflows/** and .github/actions/** to newer action majors (notably checkout@v7, cache@v6, upload-artifact@v7, download-artifact@v8 with explicit digest-mismatch, setup-node@v7, plus PR-path third-party actions).
  • Updated the issue-linking merge gate workflow logic and tests (non-closing references + paginated Development-section lookup).

Reviewed changes

Copilot reviewed 73 out of 73 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
specs/36850-upgrade-github-actions-node-24/spec.md Spec artifact (not reviewed; per PR description)
specs/36850-upgrade-github-actions-node-24/data-model.md Spec artifact (not reviewed; per PR description)
specs/36850-upgrade-github-actions-node-24/contracts/check-action-versions.md Spec artifact (not reviewed; per PR description)
docs/core/GIT_WORKFLOWS.md Docs update (not reviewed; per PR description)
.github/workflows/utility_discover-docker-tags.yml Bump actions/checkout to v7
.github/workflows/tests/link-issue-to-pr.test.sh Tests for updated issue-linking workflow parsing
.github/workflows/tests/check-action-versions.test.sh Tests for the new action-version drift guard
.github/workflows/publish_docs.yml Bump checkout/setup-node pins (disabled workflow)
.github/workflows/legacy-release_sbom-generator.yaml Bump checkout to v7
.github/workflows/legacy-release_maven-release-process.yml Bump core action pins + add digest-mismatch
.github/workflows/legacy-release_comp_maven-build-docker-image.yml Bump cache/checkout/docker-login action pins
.github/workflows/issue_on-open_add-to-triage-project..yml Bump actions/github-script to v8
.github/workflows/issue_manual_label-issues.yml Bump checkout to v7
.github/workflows/issue_manual_label-customer_deployed-issues.yml Bump checkout to v7
.github/workflows/issue_comp_release-labeling.yml Bump actions/github-script to v8
.github/workflows/issue_comp_link-issue-to-pr.yml Improve linking logic (refs support + pagination fix)
.github/workflows/issue_comp_label-conditional-labeling.yml Bump actions/github-script to v8
.github/workflows/issue_comp_frontend-notify.yml Bump actions/github-script to v8
.github/workflows/issue_autodoc.yml Update checkout SHA pin + download-artifact v8 + digest-mismatch
.github/workflows/dotbot-review.yml Update checkout SHA pin to v7.0.1
.github/workflows/dotbot-act.yml Update checkout SHA pin to v7.0.1
.github/workflows/cicd_weekly-rolling-tags.yml Bump docker/login-action to v4.6.0
.github/workflows/cicd_scheduled_opensearch-phase-sweep.yml Bump checkout to v7
.github/workflows/cicd_scheduled_notify-seated-prs.yml Bump actions/github-script to v8
.github/workflows/cicd_scheduled_image-cve-scan.yml Bump upload-artifact to v7
.github/workflows/cicd_release-sdk.yml Bump checkout to v7
.github/workflows/cicd_release-cli.yml Bump checkout/setup-node/download-artifact + digest-mismatch
.github/workflows/cicd_pr_skill-lint.yml Bump checkout/setup-node SHA pins to Node24 majors
.github/workflows/cicd_pr_actions-lint.yml New PR lint workflow: guard + tests + actionlint
.github/workflows/cicd_post-workflow-reporting.yml Bump checkout + dawidd6/action-download-artifact to v24
.github/workflows/cicd_manual_publish-starter.yml Bump artifact actions + add digest-mismatch
.github/workflows/cicd_manual_build-java-base.yml Bump checkout + docker/login-action
.github/workflows/cicd_manual_build-docker-context.yml Bump checkout + docker/login-action
.github/workflows/cicd_evergreen-tracks-promote.yml Bump checkout + docker/login-action
.github/workflows/cicd_evergreen-tracks-admin.yml Bump checkout + docker/login-action
.github/workflows/cicd_comp_test-phase.yml Bump checkout + github-script
.github/workflows/cicd_comp_release-prepare-phase.yml Bump checkout
.github/workflows/cicd_comp_release-phase.yml Bump checkout + download-artifact v8 + digest-mismatch
.github/workflows/cicd_comp_publish-pr-test-image.yml Bump download-artifact v8 + digest-mismatch
.github/workflows/cicd_comp_pr-area-labeler.yml Bump checkout + github-script
.github/workflows/cicd_comp_initialize-phase.yml Bump checkout + paths-filter + dawidd6 download action
.github/workflows/cicd_comp_finalize-phase.yml Bump download/upload-artifact + digest-mismatch
.github/workflows/cicd_comp_deployment-phase.yml Bump checkout
.github/workflows/cicd_comp_cli-native-build-phase.yml Bump checkout + upload-artifact
.github/workflows/cicd_comp_changelog-site-publish-phase.yml Bump checkout
.github/workflows/cicd_comp_build-phase.yml Bump checkout
.github/workflows/cicd_comp_ai-release-notes-phase.yml Bump checkout + setup-node
.github/workflows/cicd_6-release.yml Bump checkout + docker/login-action + setup-node
.github/workflows/cicd_5-lts.yml Bump checkout
.github/workflows/cicd_4-nightly.yml Bump checkout
.github/workflows/cicd_3-trunk.yml Bump checkout
.github/workflows/ai_claude-sdk-breaking-change.yml Bump checkout
.github/workflows/ai_claude-rollback-safety.yml Bump checkout
.github/workflows/ai_claude-post-merge-test-plan.yml Bump checkout + github-script SHA pin
.github/workflows/ai_claude-orchestrator.yml Bump checkout
.github/workflows/ai_claude-backend-reviewer.yml Bump checkout
.github/scripts/check-action-versions.sh New guard: enforces action version floors + SHA/comment + required inputs
.github/actions/legacy-release/sbom-generator/action.yml Bump checkout + upload-artifact
.github/actions/core-cicd/setup-java/README.md Update README example pins
.github/actions/core-cicd/setup-java/action.yml Bump cache restore/save to v6
.github/actions/core-cicd/prepare-runner/README.md Update README example pins
.github/actions/core-cicd/maven-job/README.md Update README example pins
.github/actions/core-cicd/maven-job/action.yml Bump cache/artifact/docker-login/pnpm pins + add digest-mismatch
.github/actions/core-cicd/deployment/deploy-jfrog/README.md Update README example pins
.github/actions/core-cicd/deployment/deploy-jfrog/action.yml Bump checkout
.github/actions/core-cicd/deployment/deploy-javascript-sdk/README.md Update prose pin references
.github/actions/core-cicd/deployment/deploy-javascript-sdk/action.yml Bump checkout/setup-node/pnpm/cache pins
.github/actions/core-cicd/deployment/deploy-javadoc/README.md Update prose pin references
.github/actions/core-cicd/deployment/deploy-javadoc/action.yml Bump checkout
.github/actions/core-cicd/deployment/deploy-docker/action.yml Bump download-artifact + add digest-mismatch; bump docker/login-action
.github/actions/core-cicd/deployment/deploy-cli-npm/action.yml Bump checkout/setup-node/download-artifact + digest-mismatch
.github/actions/core-cicd/cleanup-runner/Readme.md Update README example pins
.github/actionlint.yaml Add actionlint config (self-hosted label + shellcheck suppression)

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/scripts/check-action-versions.sh Outdated
Comment thread .github/scripts/check-action-versions.sh Outdated
@nicobytes
nicobytes force-pushed the nicobytes/issue-36850-node-24-actions-sweep branch from 1a3e71f to bacd7ab Compare August 28, 2026 11:18
…6850)

Batch 1 of the Node 24 runtime sweep. Scoped to the two densest, most-
exercised files so one PR build gives full backend build + test coverage:
maven-job (19 external refs) and setup-java (6).

  actions/cache            @0057852b # v4.3.0 -> @55cc8345 # v6.1.0
  actions/cache/restore    @v4 -> @v6.1.0   (7 sites)
  actions/cache/save       @v4 -> @v6.1.0   (7 sites)
  actions/upload-artifact  @v4 -> @v7.0.1   (6 sites)
  pnpm/action-setup        @v4 -> @v6.0.10  (1 site)

Behaviour-neutral, and verified rather than assumed:

- cache v6 is an ESM internals change. Read v6's own action.yml: restore
  keeps inputs path/key/restore-keys/enableCrossOsArchive/fail-on-cache-miss/
  lookup-only and outputs cache-hit/cache-primary-key/cache-matched-key; save
  keeps path/key/upload-chunk-size/enableCrossOsArchive. So the restore->save
  handoff through steps.restore-*.outputs.cache-primary-key is untouched.
- No cache KEY changes anywhere, so existing caches stay warm and a revert
  re-reads the same keys. Nothing to re-prime.
- upload-artifact v7's new `archive` input defaults to 'true', i.e. exactly v4
  behaviour. `archive: false` is deliberately NOT set: maven-job's image.tar is
  consumed by `docker load`, and finalize-phase's workflow-data by
  dawidd6/action-download-artifact -- both need the zipped shape.
- pnpm resolves from core-web/package.json's packageManager (pnpm@10.17.1), so
  v6 (which adds pnpm 11 support) resolves the same version. Watch the pnpm
  store cache-hit on the SECOND consecutive build, not the first: that key
  embeds steps.pnpm-info.outputs.version, so a drift there would surface as a
  slow build rather than a failure.

The maven-wrapper pin stays SHA-pinned and keeps its rationale comment (a tag
can be repointed by its owner, which is how the trivy-action and
kics-github-action compromises worked). Its "not the mutable v4 tag" wording
is updated to v6 so the comment still describes reality.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch (#36850)

Batch 2 of the Node 24 runtime sweep. 14 sites, v4 -> v8.0.1.

v8 changed `digest-mismatch` from warn to **error**: a hash mismatch now fails
the run. That is the only change in this whole sweep that can turn a green
build red, so it is landed in two steps. This commit writes
`digest-mismatch: warn` explicitly at every site, making the runtime bump
provably behaviour-neutral -- exactly the pure runtime change #36850 asks for.
Flipping to `error` is a separate, revertable commit later in the sweep.

Exposure, in order:
  maven-job:266      maven-repo    -> ~/.m2/repository, multi-GB, cross-run
  maven-job:291      build-classes -> large glob, cross-run
  maven-job:277      docker-image  -> single large file
  finalize-phase:39  build-reports-* pattern: N artifacts, N chances to fail,
                     on EVERY PR and merge-queue run

The value is written out rather than inherited at all 14 sites, so a future
major cannot silently move the default again.

Verified rather than assumed:
- v5's breaking change (single-download-by-ID path flattening) applies only to
  `artifact-ids:`. Zero uses of `artifact-ids:` in the repo, so it does not apply.
- v8 no longer auto-unzips non-zip Content-Type. That only matters alongside
  `archive: false` uploads, which batch 1 deliberately avoided introducing.
- upload-artifact v7 and download-artifact v8 are bumped as a pair, as the issue
  requires; batch 1 covered the upload side.

issue_autodoc.yml:354 keeps its SHA pin, rotated to v8.0.1's SHA with a
corrected version comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2db94cc)
Batch 4 of the Node 24 runtime sweep. 61 of 62 sites: 56 float tags and 5 SHA
pins. cicd_post-workflow-reporting.yml is deliberately held back to its own
commit -- it is the repo's only workflow_run + checkout pair and carries ~4,000
runs/90d, so it gets a one-line revert of its own.

Legacy pins cleared along the way: @v2 in utility_discover-docker-tags.yml,
@V3 in legacy-release_sbom-generator.yaml and
issue_manual_label-customer_deployed-issues.yml.

v7 refuses to check out fork PR code from pull_request_target / workflow_run.
Read src/input-helper.ts and src/unsafe-pr-checkout-helper.ts at tag v7.0.1 --
two independent reasons it cannot trigger here:

1. assertSafePrCheckout is only REACHED when a custom `repository:` or an
   explicit `ref:` is supplied. A default self-checkout is skipped outright.
2. The throw needs all three: the event is pull_request_target, or workflow_run
   whose workflow_run.event starts with pull_request; the head repo id differs
   from the base repo id (a genuine fork); AND the resolved input points at the
   fork's code (`repository` == fork full_name, or `ref` matching
   ^refs/pull/[0-9]+/(head|merge)$, or the commit == a payload PR head SHA).

Site by site: cicd_publish-pr-test-image.yml has no checkout step at all and is
gated head.repo.full_name == github.repository; cicd_comp_publish-pr-test-image.yml
never checks out; dotbot-review.yml and dotbot-act.yml fire on pull_request and
issue_comment, outside the guard's event list.

So: no v6 pins, and `allow-unsafe-pr-checkout: true` is NOT added anywhere. That
input would permanently disarm a real pwn-request protection against a risk this
repo does not have.

v6 moves credentials out of .git/config into a file under $RUNNER_TEMP. Grepped:
nothing reads .git/config or extraheader or extracts the token, and no step runs
git under sudo or inside `docker run` against the workspace. All ~10 `git push`
sites run in the same job and as the same user as their checkout.

v6's only hard runner requirement (>= 2.329.0, for Docker container actions) does
not apply: there are no `container:` jobs and no `uses: docker://` in the repo.

The five SHA pins keep their style, rotated to v7.0.1's SHA with corrected
version comments -- including the two that disagreed on main, where the same
checkout SHA was labelled # v4.2.2 in issue_autodoc.yml and # v4.2.0 in the
dotbot workflows.

Running actionlint locally confirms the three "runner of actions/checkout@v2|v3
is too old" reports present on main are now resolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit fe57fcd)
Batch 5a, isolated on purpose: this action is the mechanism accepted ADR-0013
depends on, so it gets a commit that reverts alone.

ADR-0013 ("Skip Integration and Postman Tests for Frontend-Only Changes in
Merge Queue") turns on path-based test filtering in the merge queue. Its entire
implementation is this one step at cicd_comp_initialize-phase.yml, whose outputs
the "Rewrite Filter" step consumes.

Safe because v4.0.0's only change is `feat: update action runtime to node24` --
no input, output or filter-semantics change. Verified against the upstream
release rather than inferred.

One line touched. `id: filter` and the step's `if:` guard are untouched, because
the fail-open design hangs off them: "Rewrite Filter" reads
`toJSON(steps.filter.outputs)` and defaults every filter to 'true' when change
detection is disabled, so a filter that fails to resolve runs MORE tests, not
fewer. Preserving that direction matters more than the bump.

Note for anyone re-reading ADR-0013: the code has moved on since it was written.
The ADR describes a `validation-level` input and per-output `|| 'true'` defaults;
the workflow now takes `change-detection` and consumes the outputs as a JSON
object. The dependency on paths-filter's outputs is unchanged, which is what
makes this bump safe.

Still to verify on a real merge-queue run (tracked as a task, not assumed here):
that the filter resolves and a frontend-only change still skips integration and
Postman.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit bd5a9dc)
Batch 5b. These four are not in #36850's list, but the runner emits its
deprecation annotation PER JOB, listing that job's stale actions -- so without
them AC-001 fails on its own terms.

Measured on run 33122809569 before the sweep:

  Label PR / Apply Area Labels     checkout@v4, github-script@v7
  PR Test / Setup Test Matrix      checkout@v4, github-script@v7
  Finalize / Prepare Report Data   upload-artifact@v4, download-artifact@v4

github-script@v7 appears in two of the three annotated jobs. Bumping only the
six actions the issue names would clear Initial Artifact Build and leave
initialize, label-pr and test still annotated.

  actions/github-script             @v7      -> @v8       (14)
  docker/login-action               @v3/@v3.0.0 -> @v4.6.0 (12)
  dawidd6/action-download-artifact  @v6      -> @v24       (3)

v8 is a pure node24 bump: no "type": "module", still @actions/github ^6.0.0 and
@octokit/core ^5.0.1. That is the whole of what this issue needs.

Worth recording because the opposite is widely repeated: **v9 does not break
require()**. Checked at tag v9.0.0 -- src/main.ts still passes
`require: wrapRequire` and `__original_require__`, src/wrap-require.ts is a Proxy
over __non_webpack_require__ handling both bare module IDs and ./-relative paths,
and src/async-function.ts still declares `require: NodeRequire`. All three
require() sites here (require('fs') x2, plus one local-module require) would keep
working.

What v9 actually changes: @actions/github becomes ESM-only so
require('@actions/github') fails -- 0 occurrences here; and getOctokit becomes an
injected parameter, making `const`/`let getOctokit` a SyntaxError -- also 0
occurrences. The real cost is Octokit v5 -> v7 under 14 inline scripts, which is
a different review from a runtime bump. Deferred to #37194 with that scope, not
with a wrong reason.

Verified against v24's own action.yml: every input in use is still present
(github_token, workflow_search, commit, workflow_conclusion, search_artifacts,
dry_run, name, name_is_regexp, path, run_id, if_no_artifact_found) and so is the
found_artifact output. All three call sites already set
`if_no_artifact_found: warn`, so they are non-fatal by construction.

Worth a follow-up, not this PR: actions/download-artifact@v8 now supports run-id
+ github-token natively, so this dependency may be removable. It would be a
behaviour change (no name_is_regexp, no workflow_search/commit discovery), so it
belongs in #37194.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit eb6183d)
Batch 6. One line, its own commit, because this is the repo's only
workflow_run + actions/checkout pair and it runs ~4,000 times per 90 days.

The v7 fork-PR guard cannot fire here, for two independent reasons (full
analysis in the batch-4 commit):

1. This is a bare `- uses: actions/checkout@v7.0.1` with no `with:` block at
   all, so assertSafePrCheckout is never even reached -- the guard only runs
   when a custom `repository:` or explicit `ref:` is supplied.
2. On workflow_run, github.ref and github.sha resolve to the default branch,
   never the fork head, so the condition that requires the resolved input to
   point at fork code is false.

Cannot be verified before merge: it needs a real completed workflow_run for
'PR Check', and its fork path only surfaces on the first external-contributor
PR after merge. Watch that, not the green internal ones.

Blast radius if wrong is reporting only -- Slack messages and test-report check
runs. It gates no merge. And the failure mode is loud and instant: the step
fails in seconds with "Refusing to check out fork pull request code".

With this commit every actions/checkout reference under .github/ is on v7.0.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 0fb3c5e)
Batch 7. Everything still on a stale pin outside the hot path: the four
core-cicd deployment composites, the sbom-generator, and the release, manual,
scheduled and PR-lint workflows.

  actions/setup-node       @v4 / @49933ea5 -> @v7.0.0  (6)
  actions/upload-artifact  @v4 -> @v7.0.1              (4)
  actions/cache/restore    @v4 -> @v6.1.0              (1)
  actions/cache/save       @v4 -> @v6.1.0              (1)
  pnpm/action-setup        @v4 -> @v6.0.10             (1)

pnpm caching is managed manually. That precondition does not exist in this repo,
so adding the input would be verifiably dead config -- and dead config gets
cargo-culted into places where it also does nothing.

setup-node can cache two ways, and both are closed here:

1. Explicitly, via `cache:` / `cache-dependency-path`. Verified: neither input
   is set at any of the 8 call sites, nor anywhere else under .github/.
2. Automatically, added in v5, driven by packageManager / devEngines.packageManager
   in $GITHUB_WORKSPACE/package.json. setup-node reads ONLY that path, and this
   repo has no root package.json -- just a stub root package-lock.json. So
   core-web/package.json's `packageManager: pnpm@10.17.1` is invisible to it,
   and v6 narrowed automatic caching to npm anyway.

Both facts are load-bearing: (1) alone leaves v5's automatic path open, (2) alone
leaves an explicit `cache:` open. Together they establish that no setup-node
cache can collide with the manual pnpm store cache at
deploy-javascript-sdk/action.yml:123,143 -- the hazard the AC was written to
prevent. The deviation is documented in the spec and was signed off there.

deploy-javascript-sdk is the one file where setup-node precedes pnpm/action-setup
plus a manual store cache, so it is worth an eyeball on the run log rather than a
config change.

cicd_pr_skill-lint.yml keeps its SHA pin, rotated to v7.0.0's SHA with a
corrected comment.

What remains after this commit sits only in legacy or disabled workflows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit ac00712)
…les (#36850)

Batch 8+10, the tail. This completes the sweep: every in-scope action reference
under .github/, code and documentation alike, is now on a node24 major.

  legacy-release_maven-release-process.yml   cache@v4 x5, cache/restore@v4 x4
  legacy-release_comp_maven-build-docker-image.yml  cache/restore@v3
  publish_docs.yml                          setup-node@v2-beta -> v7.0.0
  7 README.md files                         8 stale usage references

cache/restore@v3 in legacy-release_comp_maven-build-docker-image.yml targeted
the v1 cache service, shut down in February 2025. It was already dead code, so
bumping it is free.

## publish_docs.yml: pin bumped, workflow deliberately not repaired

It is disabled_manually on GitHub with 0 runs in 90 days. The hardcoded
`node-version: "16.13.2"` is replaced with node-version-file: 'core-web/.nvmrc'
so the file stops asserting an EOL Node, but its `cd core-web && npm install`
still cannot work against a pnpm workspace. Converting it would be a functional
change to a dead pipeline hidden inside a runtime bump.

The follow-up should lean towards DELETING it: #37150 removed core-web/libs/dotcms
and stripped `npm run build:docs:dotcms` from this very workflow, leaving only
build:docs:dotcms-models. Half of what it existed to publish no longer exists.
legacy-release_sbom-generator.yaml is likewise disabled with 0 runs.

## READMEs

8 references across 7 files, broader than first scoped. Four are
`uses: actions/checkout@v2` code examples (maven-job, cleanup-runner, setup-java,
prepare-runner), one is a checkout@v4 example (deploy-jfrog), and three are prose
references (deploy-javascript-sdk x2, deploy-javadoc). Prose counts: a reader
copying from it reintroduces the drift.

## Deliberately left alone, tracked in #37194

- The 4 first-party actions on runs.using: 'node16' -- they need dist/ rebuilds,
  not pin edits, and a regenerated ncc bundle cannot be diff-reviewed.
- The dead `runner.os == 'Windows'` branch in maven-job (no Windows runner exists
  in any matrix).
- .github/main.workflow (Actions-v1 HCL, never executed).
- ad-m/github-push-action@master (a moving branch ref; Docker action, so the
  runtime deprecation does not apply, but the pin violates the repo's own
  Action-security guidance).

Running actionlint locally leaves 9 findings, all pre-existing and unrelated to
action versions -- 4 expression, 1 events, plus setup-python@v4,
configure-aws-credentials@v1 and the two malformed `options:` keys in the
issue-fetcher / issue-labeler action.yml files, which belong to #37194.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit c871f91)
Batch 3, landed last on purpose so it reverts alone.

Batch 2 bumped download-artifact v4 -> v8.0.1 with `digest-mismatch: warn`,
which made the runtime bump provably behaviour-neutral. This commit adopts v8's
own secure default at the same 14 sites.

The trade: today a corrupt maven-repo artifact surfaces as a baffling Maven
resolution error twenty minutes into the build. With `error` it surfaces
immediately, at the download step, naming the artifact.

If a mismatch does appear -- most likely on the multi-GB maven-repo, or on
finalize-phase's `build-reports-*` pattern where N artifacts mean N chances --
revert THIS commit only. The Node 24 migration stays landed.

Verify on two consecutive PR builds plus a merge-queue run before trusting it;
one green build is not evidence for a hash check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a12c41e)
…36850)

The merge of main added .github/workflows/ai_claude-post-merge-test-plan.yml,
which arrived with two stale SHA pins:

  actions/checkout      @11d5960a # v4.4.0 -> @3d3c42e5 # v7.0.1
  actions/github-script @60a0d830 # v7.0.1 -> @ed597411 # v8

Both entered through the merge rather than through the sweep, and neither would
have been spotted by eye in a 74-file diff.

Note the second one also had a wrong version comment -- the SHA was labelled
# v7.0.1, which is checkout's version, not github-script's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7a0c4c7)
@nicobytes
nicobytes force-pushed the nicobytes/issue-36850-node-24-actions-sweep branch from bacd7ab to f6a9d26 Compare August 28, 2026 11:21
hmoreras
hmoreras previously approved these changes Aug 28, 2026
@nicobytes
nicobytes dismissed hmoreras’s stale review August 28, 2026 15:50

The merge-base changed after approval.

nicobytes and others added 3 commits August 28, 2026 12:50
…6850)

peter-evans/create-or-update-comment @v4 (node20) -> @v5.0.0 (node24), at both
sites in issue_comp_link-issue-to-pr.yml.

This was a scoping mistake, not a deferral. It was filed under "cold-path
third-party actions" for #37194, but it runs on **every pull request** via
`Add Issue to PR / link-issue`, so it kept AC-001 unsatisfied: the annotation
scan on this PR still reported

  Node.js 20 is deprecated. ... peter-evans/create-or-update-comment@v4

The cause: the PR-pipeline surface was traced through cicd_1-pr.yml's job graph
only. Other workflows also fire on `pull_request` -- issue_open-pr.yml (which
calls this composite), the five ai_claude-* workflows, dotbot-review.yml and
cicd_pr_skill-lint.yml -- and they were not walked.

Safe: v5.0.0's release notes are dependency bumps only, and every input in use
here (issue-number, comment-id, body, edit-mode) is still present in v5's
action.yml. The `edit-mode: replace` guard against comment accumulation
(issue #35794) is untouched.

Remaining Node 20 annotations on this PR are NOT fixable from this repository:
`Claude AI Orchestrator` and `Claude AI Rollback Safety Check` run
dotCMS/ai-workflows' reusable workflows, addressed by dotCMS/ai-workflows#67.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread .github/workflows/issue_comp_link-issue-to-pr.yml Outdated
Comment thread .github/workflows/issue_comp_link-issue-to-pr.yml Outdated
Semgrep flagged the v5.0.0 tag as blocking on the line the previous commit
introduced, and the finding is correct on its own terms: this action runs on
every pull request, receives repository-derived values (issue-number,
comment-id, body), and holds a token that can modify issue comments. A
repointed tag would execute attacker code in that context with no change to
this workflow file.

  @v5.0.0 -> @e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0  (node24)

Pinned rather than triaged because it costs two lines here, unlike the repo-wide
convention change. This joins the existing deliberate SHA pins (maven-job's
cache, issue_autodoc, dotbot-*, cicd_pr_skill-lint) under the same reasoning as
the comment at maven-job/action.yml: a tag can be repointed by its owner, which
is how the trivy-action and kics-github-action compromises worked.

Note this does not resolve Semgrep's other ~146 findings of the same rule. Those
are pre-existing: main carries 199 mutable tags and this branch carries 199 too,
because @v4 -> @v7.0.1 swaps one mutable tag for another. Semgrep's scan is
diff-aware, so a touched line is reported as new. Converting all 199 to SHAs is a
convention migration that belongs with #37194's dependabot work.

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

Area : CI/CD PR changes GitHub Actions/workflows Area : Documentation PR changes documentation files

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Upgrade GitHub Actions to Node 24 runtime majors

4 participants