116 lines
10 KiB
Markdown
116 lines
10 KiB
Markdown
---
|
||
created: 2026-08-07
|
||
status: draft
|
||
---
|
||
|
||
# Implementation Plan: Upgrade GitHub Actions to the Node 24 runtime
|
||
|
||
## Overview
|
||
|
||
GitHub deprecated the Node 20 action runtime; every pinned action whose
|
||
`action.yml` declares `runs.using: node20` now runs forced on Node 24 and
|
||
emits a deprecation warning on every workflow run. This plan upgrades every
|
||
Node-20 action pin in `.github/workflows/` to a SHA-pinned release that
|
||
declares `runs.using: node24`, verifying each target's runtime and
|
||
backwards-compatibility before landing. Order: upgrade the plain-JavaScript
|
||
actions (checkout, artifacts, cache, github-script) first, then the docker/*
|
||
family, then the remaining third-party actions, and finish with a full audit
|
||
that no `node20` pin remains.
|
||
|
||
There is no product spec for this change: it is CI-infrastructure hygiene with
|
||
no user-visible behavior. Validation is mechanical — see
|
||
[audit.md](audit.md) for the runtime-check procedure used to build the table
|
||
below.
|
||
|
||
## Target version table
|
||
|
||
Verified on 2026-08-07 via the pinned SHA's `action.yml` `runs.using` field
|
||
(`audit.md` documents the exact method). "Target" is the release whose pinned
|
||
commit declares `node24`.
|
||
|
||
| Action | Current pin | Uses | Target | Target SHA | Runtime |
|
||
|---|---|---|---|---|---|
|
||
| actions/checkout | `34e114876b0b11c390a56381ad16ebd13914f8d5 # v4` | 7 | v6 (already used in repo) | `df4cb1c069e1874edd31b4311f1884172cec0e10` | node24 |
|
||
| actions/upload-artifact | `ea165f8d65b6e75b540449e92b4886f43607fa02 # v4` | 2 | v7.0.1 | `043fb46d1a93c77aae656e7c1c64a875d1fc6a0a` | node24 |
|
||
| actions/upload-artifact | `330a01c490aca151604b8cf639adc76d48f6c5d4 # v5` | 13 | v7.0.1 | `043fb46d1a93c77aae656e7c1c64a875d1fc6a0a` | node24 |
|
||
| actions/download-artifact | `d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4` | 2 | v8.0.1 | `3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c` | node24 |
|
||
| actions/download-artifact | `634f93cb2916e3fdff6788551b99b062d0335ce0 # v5` | 12 | v8.0.1 | `3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c` | node24 |
|
||
| actions/cache | `0057852bfaa89a56745cba8c7296529d2fc39830 # v4` | 8 | v6.1.0 | `55cc8345863c7cc4c66a329aec7e433d2d1c52a9` | node24 |
|
||
| actions/github-script | `f28e40c7f34bde8b3046d885e986cb6290c5673b # v7` | 4 | v9.0.0 | `3a2844b7e9c422d3c10d287c895573f7108da1b3` | node24 |
|
||
| docker/login-action | `c94ce9fb468520275223c153574b00df6fe4bcc9 # v3` | 9 | v4.6.0 | `dbcb813823bdd20940b903addbd779551569679f` | node24 |
|
||
| docker/setup-buildx-action | `8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3` | 8 | v4.2.0 | `bb05f3f5519dd87d3ba754cc423b652a5edd6d2c` | node24 |
|
||
| docker/build-push-action | `10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6` | 7 | v7.3.0 | `53b7df96c91f9c12dcc8a07bcb9ccacbed38856a` | node24 |
|
||
| docker/metadata-action | `c299e40c65443455700f0fdfc63efafe5b349051 # v5` | 5 | v6.2.0 | `dc802804100637a589fabce1cb79ff13a1411302` | node24 |
|
||
| softprops/action-gh-release | `3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2` | 1 | v3.0.2 | `3d0d9888cb7fd7b750713d6e236d1fcb99157228` | node24 |
|
||
| cloudflare/wrangler-action | `9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3` | 1 | v4.0.0 | `ebbaa1584979971c8614a24965b4405ff95890e0` | node24 |
|
||
| amannn/action-semantic-pull-request | `e32d7e603df1aa1ba07e981f2a23455dee596825 # v5` | 1 | v6.1.1 | `48f256284bd46cdaab1048c3721360e808335d50` | node24 |
|
||
| actions/deploy-pages | `d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4` | 1 | v5.0.0 | `cd2ce8fcbc39b97be8ca5fce6e763baed58fa128` | node24 |
|
||
| actions/configure-pages | `983d7736d9b0ae728b81ab479565c72886d7745b # v5` | 1 | v6.0.0 | `45bfe0192ca1faeb007ade9deae92b16b8254a0d` | node24 |
|
||
| actions/upload-pages-artifact | `56afc609e74202658d3ffba0e8f6dda462b719fa # v3` | 1 | v5.0.0 | `fc324d3547104276b827a68afc52ff2a11cc49c9` | composite (inner upload-artifact v7.0.0 = node24) |
|
||
| robherley/go-test-action | `3856e4e57831b3b6d6d8d89f91747e5200c533f7 # v0` | 1 | v1.1.0 | `2f859e0c8769d755d3174eecb9af8f64660827f3` | node24 |
|
||
|
||
Already node24 / no change: `actions/checkout` v6 (`df4cb1c…`), `actions/setup-node` v6 (`48b55a0…`), `actions/setup-go` v6 (`924ae3a…`), `pnpm/action-setup` v5 (`a8198c4…`), `crazy-max/ghaction-import-gpg` v7.0.0 (`2dc316d…`), `actions/create-github-app-token` v3.2.0 (`bcd2ba4…`), `Swatinem/rust-cache` v2 (`42dc69e…`). Composite actions with no direct runtime (`anthropics/claude-code-action`, `orhun/git-cliff-action`, `dtolnay/rust-toolchain`) are left as-is.
|
||
|
||
## Compatibility notes (per action)
|
||
|
||
- `actions/checkout` v4→v6: v6 already used 34× in this repo — proven.
|
||
- `actions/upload-artifact` v4/v5→v7.0.1 and `actions/download-artifact` v4/v5→v8.0.1: the artifact backend is shared across the v4+ majors on GitHub-hosted runners; upload/download must stay on compatible majors (v7 upload + v8 download both use the v4 artifact API). All uses move together in the same PR, so cross-job download/upload pairing is preserved.
|
||
- `actions/github-script` v7→v9.0.0: v9 breaking change is `require('@actions/github')` no longer working; all four uses in this repo only use injected `github`/`context` — audited, no `require('@actions/github')` in any script block.
|
||
- `docker/login-action` v3→v4.6.0, `docker/setup-buildx-action` v3→v4.2.0: Node 24 runtime + ESM; v4 removed deprecated inputs/outputs — repo uses none (`with:` blocks checked).
|
||
- `docker/build-push-action` v6→v7.3.0: v7 removed deprecated `DOCKER_BUILD_NO_SUMMARY` / `DOCKER_BUILD_EXPORT_RETENTION_DAYS` envs — not used in repo.
|
||
- `docker/metadata-action` v5→v6.2.0: list inputs now preserve `#` inside values — release.yml uses no `#` in tags/images lists.
|
||
- `softprops/action-gh-release` v2→v3.0.2: Node 24 only; `files:`/`body_path:` inputs unchanged in v3.
|
||
- `cloudflare/wrangler-action` v3→v4.0.0: default wrangler version changes to v4 (`latest`), but repo pins `wranglerVersion: 3.90.0` explicitly — behavior preserved.
|
||
- `amannn/action-semantic-pull-request` v5→v6.1.1: Node 24 + ESM; inputs unchanged.
|
||
- `actions/deploy-pages` v4→v5.0.0, `actions/configure-pages` v5→v6.0.0, `actions/upload-pages-artifact` v3→v5.0.0: Pages trio moved together; v5 upload-pages-artifact's inner `actions/upload-artifact` is v7.0.0 (node24) instead of v4 (node20).
|
||
- `robherley/go-test-action` v0→v1.1.0: Node 24; `fromJSONFile` input retained.
|
||
|
||
## Tasks
|
||
|
||
Task files are grouped by workflow file so each task touches a disjoint set of
|
||
files (safe to run in any order; wave labels only express suggested order):
|
||
|
||
```
|
||
Wave 1 (parallel candidates — user authorization required):
|
||
- [x] [task-01-test-workflows](task-01-test-workflows.md) — backend-tests.yml, frontend-tests.yml, cargo-audit.yml, e2e-tests.yml
|
||
- [x] [task-02-container-workflows](task-02-container-workflows.md) — universal-rebuild.yml, ci-base-image.yml
|
||
- [x] [task-03-release-workflow](task-03-release-workflow.md) — release.yml (all Node-20 pins)
|
||
- [x] [task-04-review-misc-workflows](task-04-review-misc-workflows.md) — claude.yml, claude-code-review.yml, opencode-code-review.yml, notify-docs.yml, preview-env.yml, pr-title.yml, plugin-registry-index.yml
|
||
|
||
Wave 2:
|
||
- [x] [task-05-verify](task-05-verify.md) — runtime audit + lint + contract tests + YAML sanity
|
||
```
|
||
|
||
## Tests
|
||
|
||
No product behavior is testable — the changed artifacts are workflow YAML only. Pre-PR validation:
|
||
|
||
- **Runtime audit:** rerun the procedure in [audit.md](audit.md) (fetch each pinned `action.yml`, assert `runs.using` is `node24` or `composite`) and confirm zero `node20` pins remain.
|
||
- **Pinning lint:** `python3 .github/scripts/lint-action-pinning.py` and `python3 .github/scripts/lint-action-pinning_test.py` still pass (every new ref is a 40-char SHA).
|
||
- **Workflow contract tests:** `python3 .github/scripts/release-workflow-contract_test.py`, `claude-code-review-workflow-contract_test.py`, `preview-env-workflow-contract_test.py` — the two SHA-asserting contract tests only pin `crazy-max/ghaction-import-gpg@2dc316d…` and `actions/setup-node@48b55a0…`, which are unchanged.
|
||
- **YAML sanity:** `python3 -c "import yaml,sys; [yaml.safe_load(open(f)) for f in sys.argv[1:]]"` over every changed workflow.
|
||
- **Runtime smoke (post-PR):** watch the first `backend-tests`/`e2e-tests`/`frontend-tests` run for the Node-20 deprecation warning; confirm it is gone.
|
||
|
||
## Verification Results
|
||
|
||
Complete 2026-08-07. All five tasks done; every task's `## Results` records the exact commands and outcomes.
|
||
|
||
- Applied 84 `uses:` replacements across 14 workflow files (checkout v4→v6, upload/download-artifact v4/v5→v7.0.1/v8.0.1, cache v4→v6.1.0, github-script v7→v9.0.0, docker login/buildx/build-push/metadata v3/v5/v6→v4.6.0/v4.2.0/v7.3.0/v6.2.0, gh-release v2→v3.0.2, wrangler v3→v4.0.0, semantic-pull-request v5→v6.1.1, Pages trio → v6.0.0/v5.0.0/v5.0.0, go-test-action v0→v1.1.0).
|
||
- Final runtime audit (`/tmp/opencode/audit_final.py`): 25 distinct pinned actions; every `runs.using` is `node24` or `composite` — **zero `node20` pins remain**. The three annotated-tag-object pins (rust-cache, claude-code-action, pnpm/action-setup) were dereferenced to their commits and re-verified (node24 / composite / node24).
|
||
- `python3 .github/scripts/lint-action-pinning.py` → "✓ All 18 workflow file(s) use SHA-pinned action refs."; `lint-action-pinning_test.py` → 9 OK.
|
||
- Contract tests: `release-workflow-contract_test.py` → 24 OK, `claude-code-review-workflow-contract_test.py` → 9 OK, `preview-env-workflow-contract_test.py` → 1 OK.
|
||
- YAML parse: 18/18 workflows OK; `git diff --check` clean.
|
||
- Post-merge smoke: pending — first `backend-tests`/`e2e-tests`/`frontend-tests` run after merge should no longer show the Node-20 deprecation warning.
|
||
|
||
## PR Fixup (2026-08-07)
|
||
|
||
PR #2401 (`ci: upgrade GitHub Actions to Node 24 runtime`):
|
||
|
||
- CodeRabbit/zizmor flagged the two v4→v6 checkout blocks in the arm64 release jobs (`docker-arm64`, `docker-universal-arm64`) for missing `persist-credentials: false` (artipacked — the checked-out tree feeds the Docker build context). Fixed in `b7624fd` (`ci: disable checkout credential persistence in arm64 release jobs`), scoped to exactly the two changed blocks; the other release.yml checkouts already set the flag.
|
||
- Final head `b7624fd32e43828c0ff50b90e73013fda3bf774e`: 47 checks passed, 0 failed, 0 pending, `mergeable_state: clean`, unresolved threads 0, hidden threads 0, actionable issue comments 0.
|
||
- Post-merge smoke still pending as above.
|
||
|
||
## Open Questions
|
||
|
||
None — target SHAs and compatibility are resolved in the table above.
|