On September 23, 2026, GitHub removed the Node.js 20 runtime from Actions runners. Any JavaScript action whose action.yml still declares runs.using: node20 can no longer start: the job fails at action startup, before your tests even run.
Timeline (from the official changelog):
- 2025-09-19: deprecation announced
- 2026-06-16: node20 actions force-run on Node 24 with a warning
- 2026-09-23: Node 20 removed entirely (revised up from Sept 16)
If your CI went red yesterday and you haven't touched anything: this is why. Here's how to triage it in 15 minutes.
Check 1: Confirm you're hit (1 min)
Open your latest workflow run logs and search for: Node.js 20 actions are deprecated
Hit = your pins are stale.
Check 2: Inventory every action pin (5 min)
grep -rhoE 'uses:[[:space:]]*[^[:space:]#]+' .github/workflows/*.yml .github/actions/*/action.yml 2>/dev/null | sort -u
Note the second path: composite actions live in .github/actions/ and are invisible to Dependabot-style tag scans. You must grep them separately.
Check 3: Resolve each pin's actual runtime (5 min)
Do not trust tag names. The node24 boundary is not uniform across majors: upload-artifact only moved to node24 at v6, download-artifact at v7. For each third-party pin:
gh api 'repos/<owner>/<repo>/contents/action.yml?ref=<ref>' --jq '.content' | base64 -d | grep -A1 '^runs:'
using: node20 = broken. using: node24 = safe. using: composite or using: docker = unaffected.
Check 4: Self-hosted runners: check the floor (2 min)
node24 actions require runner >= 2.327.1. Below that, bumped actions fail even with correct pins. Also: Node 24 doesn't run on macOS <= 13.4 or ARM32 at all: those jobs need a different plan.
The bumps everyone hits
| Action | Broken pin | Minimal node24 major | Watch out |
|---|---|---|---|
actions/checkout |
v4 | v5 | v6 moves credentials out of .git/config; v7 blocks fork-PR checkout by default |
actions/setup-node |
v4 | v5 | v5 enables package-manager caching by default: disable with package-manager-cache: false in privileged workflows |
actions/setup-python |
v5 | v6 | Clean bump, runtime only |
actions/setup-go |
v5 | v6 | v6 honors the toolchain directive and defaults GOTOOLCHAIN=local: use go-version-file: 'go.mod' or builds break |
actions/upload-artifact |
v4 | v6 | Do NOT stop at v5: it still runs node20 by default |
actions/download-artifact |
v4 | v7 | v5 changed single-artifact download paths; pair with upload v6 |
Prefer the minimal node24 major: each extra major crossed is unrelated breaking change you absorb for nothing.
The two gotchas that bite backend repos hardest
setup-go@v6 silently changes which Go you build with. v6 honors the toolchain directive in go.mod (v5 ignored it) and defaults GOTOOLCHAIN=local, which forbids the silent toolchain downloads v5 allowed. If your workflow pins go-version: 1.24 but go.mod says go 1.25, builds fail. Fix: go-version-file: 'go.mod'.
There is no rollback to node20. The runtime is gone: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 and friends do nothing now. "Rollback" means retreating to a different node24 major, never back to the old pin.
I spent today turning this into a complete one-afternoon migration kit: all 19 actions in the pin-bump map with landmines, the ordered 7-step migration, 6 code-review prompts, a testing/verification plan, the honest rollback plan, and every gotcha above sourced. It's $39 here: https://kmanzer.gumroad.com/l/sreej
Every version number in it was verified against upstream release notes or real migration reports on the build date. Good luck out there: and check your composite actions.
Top comments (0)