Skip to content

ci: pin every action to a Node 24 major, by commit SHA - #153

Open
Kartikey1306 wants to merge 9 commits into
embeddedos-org:masterfrom
Kartikey1306:ci/actions-node24
Open

Kartikey1306 wants to merge 9 commits into
embeddedos-org:masterfrom
Kartikey1306:ci/actions-node24

Conversation

@Kartikey1306

Copy link
Copy Markdown
Contributor

Stacked on #132 (origin/master does not pass its own checks without it, and this PR should not wear those reds). Review 4054494..7ed4e39: one commit, 10 files, 55 uses: lines, nothing else. Land #132 first; this then rebases to its single commit.

Closes #152

Why now, precisely

Every job in this repository logs, on every run: "Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@v4, actions/setup-python@v5, …" — 21 such warnings on a single ebuild run (35207728484).

GitHub's changelog (2025-09-19, editor's note of 2026-08-25), quoted: "Beginning on June 16th, 2026, runners will begin using Node24 by default"; the opt-out ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true "will only work until we upgrade the runner and remove Node20 on September 23rd, 2026." What the changelog does not say is whether a node20-declared action errors after the 23rd — today ours run on the forced Node 24 and pass. This PR does not claim they will break; it removes the dependence on a fallback GitHub calls temporary, six days before it ends.

What changed

55 uses: references in 10 files, each moved from a major whose action.yml declares node20 to the current major that declares node24 — pinned by commit SHA with the release tag in a comment (OpenSSF Pinned-Dependencies, the same form scorecard.yml already uses). Each SHA was resolved from the exact release tag through the GitHub API and confirmed identical to what the floating major tag points at:

action pinned to tag runs.using
actions/checkout 3d3c42e v7.0.1 node24
actions/configure-pages 45bfe01 v6.0.0 node24
actions/deploy-pages 368f825 v5.0.1 node24
actions/download-artifact 3e5f45b v8.0.1 node24
actions/setup-python 5fda3b9 v7.0.0 node24
actions/upload-artifact 043fb46 v7.0.1 node24
actions/upload-pages-artifact fc324d3 v5.0.0 composite
codecov/codecov-action 303a32d v7.1.1 composite
github/codeql-action b96794f v4.38.0 composite
softprops/action-gh-release efb3536 v3.0.3 node24

(upload-pages-artifact and codeql-action/codecov are composites; the first now wraps a SHA-pinned Node-24 upload-artifact, the others run Node-24 tooling.)

Breaking-change audit, per action, against this repository's actual usages

Read from each action's release notes at every major boundary crossed, then checked against every with: block here:

  • download-artifact v4→v8 — v5 changed the extracted path for single artifacts downloaded by ID; every use here is by name: or by pattern: + merge-multiple: true, the cases the notes mark "no action needed". v8 defaults digest-mismatch to error (a corrupted download now fails instead of warning) — wanted.
  • github-script v7→v9 — v9 breaks require('@actions/github'); the only script here uses the injected github/context objects and no require.
  • codecov-action v4→v7 — v5 replaced the uploader with the CLI wrapper and deprecated filefiles; usage here is files: + flags:, both still accepted.
  • codeql-action v3→v4 — minimum bundle 2.19.4, met on hosted runners; init's languages/config-file inputs unchanged. The check name comes from this workflow's name: keys, not the action, so the required-check registry is unaffected — confirmed on this PR's run.
  • configure-pages v4→v6 — v5's breaking changes concern Next.js static-export configuration; usage here passes no inputs.
  • checkout v4→v7, setup-python v5→v7, upload-artifact v4→v7, cache v4→v6, deploy-pages v4→v5, action-gh-release v2→v3 — runtime-only majors; require runner ≥ 2.327.1, which every hosted image here (*-latest, ubuntu-22.04) meets. No self-hosted runner exists in this org (grepped all three repos).

Verification

Every file parsed before and after; the parsed structure asserted equal apart from uses: valueson: triggers, job names, job-level keys, step count and every non-uses step key identical; action names unchanged; line endings unchanged. Not yaml.safe_load succeeding — the assertion.

pytest 680 passed, 1 skipped; the 3 tests that invoke the C compiler (test_ninja_backend, test_build_dir_resolution end-to-end, test_footprint real binary) are NOT RUN locally — Xcode license on this host — and fail identically on the base without this change; CI runs them.

Which files this PR's own run exercises, and which it cannot:

file uses: bumped triggers evidence
ci.yml 9 push, pull_request runs on this PR
codeql.yml 5 push, pull_request, schedule runs on this PR
deploy-pages.yml 4 push, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next push run after merge
nightly.yml 9 schedule, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next schedule run after merge
release.yml 15 push, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next push run after merge
simulation-test.yml 4 push, pull_request, schedule, workflow_dispatch runs on this PR
sync-release-branch.yml 1 push NOT RUN here — no pull_request trigger; first exercised by its own next push run after merge
vendor-drift.yml 2 push, pull_request, schedule, workflow_dispatch runs on this PR
video-build.yml 4 push, release, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next push/release run after merge
weekly.yml 2 schedule, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next schedule run after merge

The NOT RUN rows are stated rather than implied: their first execution with these pins is their next scheduled/release/dispatch run after merge, and a failure there would be attributable to this change.

Deliberately not touched — 4 files, to keep the queue mergeable

These also carry Node-20 pins but are modified by open pull requests; bumping them here would put every one of those PRs into CONFLICTING. They follow in one commit once the PRs below land:

file open PRs modifying it
auto-assign.yml #132, #144, #146, #148, #151
book-build.yml #148
eosim-sanity.yml #121, #151
scorecard.yml #132, #144, #146, #148, #151

CI -- ebuild has been red on master since the 09-08 batch merge, and the
first failing step (ruff) has hidden the ones behind it.

Lint (ruff, all nine Test legs):
- test_build_dir_resolution.py imported shutil twice (F811).
- test_package_recipe.py lost its trailing newline (W292).
- test_ci_gate.py had `import itertools` / `import re` two hundred lines
  down (E402) -- my own embeddedos-org#103, replayed onto a file that had moved.
  These three hunks are byte-identical to embeddedos-org#122's, so either PR merging
  first leaves the other clean.

Type check and tests (never reached on master since 09-08):
- ebuild/packages/index_sync.py calls PackageRecipe.to_dict(), which embeddedos-org#111
  defined and embeddedos-org#112 -- merged five minutes later from a base without it --
  deleted in its replay. mypy names it once; pytest fails nine
  test_index_sync cases with AttributeError. The method is restored verbatim
  from embeddedos-org#111 (cc90078): it emits the `package:`/`build:` keys parse_recipe()
  reads back, which an asdict() replacement would not.

Vendored core drift:
- embeddedos-org#109 (dba3d83) edited core/eos/docs/three-way-alignment.md, a vendored copy
  pinned to eos 5544c98, so drift went 44 -> 45 and the guard failed as
  designed. Reverted to the pinned content (blob 7f9c8c1, the same bytes as
  eos:docs/three-way-alignment.md at the pin). The alignment note belongs in
  ebuild's own docs or upstream in eos, not in the snapshot.

OSSF Scorecard:
- ossf/scorecard-action@v2.4.0 pulls gcr.io/openssf/scorecard-action, and
  gcr.io now refuses the pull ("requires billing to be enabled"). v2.4.3
  pulls from ghcr.io; eos already pins it and its Scorecard job is green.

Not in this PR: EoSim Sanity's Windows/macOS legs install a wheel that has
never been published; embeddedos-org#121 (srpatcha) already replaces that with the clone
the other legs use.

Verified locally: ruff clean, yamllint clean, mypy clean over 107 files,
680 passed / 1 skipped, scripts/check_vendor_drift.py 44/44 and 46/46.
…/3.11, yamllint on Windows

Both surfaced on this branch's first CI run, once ruff let the job get past
its first step.

- ebuild/plugins/__init__.py: on Python 3.10 and 3.11 the stubs type
  entry_points() as the deprecated mapping, and its .get() wants an
  EntryPoints default, so mypy fails with arg-type. The line carried a
  '# type: ignore[attr-defined]' -- the wrong error code, so it suppressed
  nothing. Spelled out with a cast, byte-identical to embeddedos-org#122's hunk (54605f0).

- .yamllint.yml: the Windows runners check out with core.autocrlf=true, so
  every YAML file arrives as CRLF and the default new-lines: unix rule
  rejected every line. The step was added on 09-03 and had never passed on
  that leg. new-lines: platform accepts the checkout's own convention.
…e checkout's line ending

new-lines: platform was the wrong fix. The Windows runners' autocrlf turns
LF files into CRLF -- except a file that already carries a stray CR, which
git leaves alone, and auto-assign.yml had one on its last line. So under
'platform' Windows expected CRLF and got LF on that file's first line, and
the leg was red again for the opposite reason.

Pin *.yml and *.yaml to eol=lf so every OS lints the same bytes, keep
yamllint's default unix rule, and drop the stray CR.
…h a round-trip test

PackageRecipe.to_dict() was written before install_args existed and was
never taught about it, so a recipe that went through index_sync came back
from the cache with install_args empty while every other field survived.
It also returned the recipe's own list objects, so a caller that appended
to what it got back edited the recipe behind its back.

install_args is now emitted after build_args, matching the order
parse_recipe() reads them, and every list field is copied on the way out.
index_sync's entry-to-recipe mapping carries install_args too; without
that the field could not arrive from an index at all.

The new round-trip test builds a recipe with every field set and asserts
parse_recipe(safe_load(safe_dump(to_dict()))) equals it; against the
previous to_dict() it fails on install_args. A second test checks the
lists are copies, and test_index_sync gains a case that an index entry's
install_args reaches the cached YAML.
actions/checkout, ossf/scorecard-action and codeql-action/upload-sarif
were referenced by moving tags. A tag can be re-pointed; a commit cannot,
and Scorecard itself flags unpinned actions. Each is now pinned to the
commit its tag resolved to on 2026-09-14, with the tag kept in a trailing
comment, the same shape linked-issue.yml already uses.
The changelog records what the master repair changed and why. CONTRIBUTING
gains a note for Windows contributors: .gitattributes now pins YAML to LF,
but the attribute governs future checkouts and commits, not files already
in a working tree, so an existing clone needs one git add --renormalize .
(or a fresh clone) before yamllint stops seeing CRLF.
…working tree

git add --renormalize . re-applies the clean filter to the index; it never
rewrites files, so a clone with CRLF YAML still has CRLF YAML afterwards
(reproduced in a scratch clone with core.autocrlf=true: two CRs before,
two after, status clean). What re-checks the files out is
git rm --cached -r . && git reset --hard HEAD, or a fresh clone. CONTRIBUTING
and the changelog now say that. The changelog entry also lists every file
it describes and the plugins/__init__.py type-check fix it had left out, and
the to_dict() comment no longer claims to follow parse_recipe()'s order.
The review at 43b0337 found the one-line assertion pinning install_args
next to build_args and, six lines above it, recipe.py's docstring saying
key order does not matter. Both are true: order is not a correctness
property, because parse_recipe() reads every key by name, and it is a
stability property, because index_sync writes the dict as cached YAML that
humans diff. The assertion now says which of the two it is guarding and
what to do when a reordering is deliberate, so a future failure reads as
intentional rather than as a mystery.

No behaviour change. ruff clean; pytest 683 passed, 1 skipped.
Every job logs "Node.js 20 is deprecated. The following actions target
Node.js 20 but are being forced to run on Node.js 24". GitHub's changelog
(2025-09-19, editor's note 2026-08-25): Node 24 is the default since
2026-06-16, and the Node 20 opt-out stops working when Node 20 is removed
from the runners on 2026-09-23. Whether a node20-declared action then errors
is not stated; today they run on the forced Node 24 and pass. This moves
55 uses: references in 10 workflow files to majors that declare
node24, so nothing here depends on a fallback GitHub calls temporary.

Pinned by commit SHA with the release tag in a comment (OpenSSF
Pinned-Dependencies), each SHA resolved from the exact release tag and
confirmed identical to what the floating major tag points at:

  actions/checkout                   -> 3d3c42e  # v7.0.1  (runs.using: node24)
  actions/configure-pages            -> 45bfe01  # v6.0.0  (runs.using: node24)
  actions/deploy-pages               -> 368f825  # v5.0.1  (runs.using: node24)
  actions/download-artifact          -> 3e5f45b  # v8.0.1  (runs.using: node24)
  actions/setup-python               -> 5fda3b9  # v7.0.0  (runs.using: node24)
  actions/upload-artifact            -> 043fb46  # v7.0.1  (runs.using: node24)
  actions/upload-pages-artifact      -> fc324d3  # v5.0.0  (runs.using: composite)
  codecov/codecov-action             -> 303a32d  # v7.1.1  (runs.using: composite)
  github/codeql-action               -> b96794f  # v4.38.0  (runs.using: composite)
  softprops/action-gh-release        -> efb3536  # v3.0.3  (runs.using: node24)

Breaking-change audit against this repository's usages, from each action's
release notes: download-artifact is used by name or by pattern with
merge-multiple (v5's single-ID path change: "no action needed"; v8's
digest-mismatch=error default is wanted); github-script scripts use only
the injected github/context objects, never require(); codecov uses
files/flags, both still accepted; codeql-action v4's bundle minimum is met
on hosted runners and init's languages/config-file inputs are unchanged;
configure-pages v5's break is Next.js-only; checkout/setup-python/
upload-artifact/cache/deploy-pages/gh-release majors are runtime-only
(runner >= 2.327.1, met by every hosted image used here; no self-hosted
runner exists in this org).

Only workflow files that no open pull request modifies are touched, so
this cannot conflict with the review queue; the rest are listed in the PR
and follow once those PRs land.

Verified: every file parsed before and after, with the parsed structure
asserted equal apart from uses: values -- triggers, job names, job-level
keys, step count and every non-uses step key unchanged, line endings
unchanged. pytest 680 passed, 1 skipped; 3 tests that invoke the C compiler NOT RUN locally (Xcode license on this host) — they fail identically on the base without this change.

Closes embeddedos-org#152
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Run evidence for this head (7ed4e39), per bumped file — 30 checks, 0 failing.

file run result
ci.yml 35211509329 success — the only bumped file here with codecov-action@v7; the upload step ran with files:/flags: accepted
codeql.yml 35211509357 success — check names unchanged
simulation-test.yml 35211509322 success
vendor-drift.yml 35211509279 success

auto-assign.yml is deferred (#132's stack), not changed here; its skipped run is the fork if:, unrelated.

The other 6 bumped files (deploy-pages, nightly, release, sync-release-branch, video-build, weekly) have no pull_request trigger: NOT RUN, first exercised after merge. The 3 compiler-invoking tests I could not run locally (Xcode license) ran in CI — ebuild above and passed.

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.

Every workflow runs actions pinned to Node 20 majors; GitHub removes Node 20 from runners on 2026-09-23

2 participants