GitHub Actions Node 20 removal: what happens to node20 actions now
GitHub removed Node 20 from its GitHub Actions runners on September 23, 2026. The changelog entry calls itself “the final notification”: runners “now use Node 24 for JavaScript actions”, and the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION opt-out that GitHub had offered for the months after the switch “is no longer available”. The GitHub Actions Node 20 removal closes a deprecation that GitHub announced on September 19, 2025, and whose dates moved three times.
The entry gives two instructions. If you maintain a JavaScript action, change its runs.using to node24 and publish a release. If you use actions, move to versions that support Node 24.
It also says the newest versions of all first-party actions “were updated to use Node 24”, without naming them.
Two questions follow from that. What does a runner do today with an action that still declares node20? And which of the actions a typical workflow pins still do?
Advertisements
For this news piece, the runner’s source at its current release answers the first. The runs.using value of every major tag from v3 of nine official actions, 46 tags, of eight popular third-party actions and of the Node.js starter workflow GitHub itself publishes answers the second.
The short version: your node20 actions still run, on Node 24, with a warning in the job. Whether they work is up to their code.
This is about the Node that runs an action’s own code, not the Node your project uses. runs.using in an action’s metadata picks the runtime for that action. The node-version input of setup-node picks the Node for the steps that follow, and its README accepts specs such as “12.x, 10.15.1, >=10.15.0”. A job can still build and test a project on Node 20, or on Node 12, on a runner whose actions run on Node 24.
Advertisements
The runner forces a node20 action onto Node 24 instead of refusing it
The phrase “no longer available” reads like a failure. The runner’s version-selection code, at version 2.338.0 of October 6, 2026, describes something else.
The version choice has three phases. Two feature flags, which the runner reads from the job’s variables, select the later two; with neither set, Phase 1 applies and the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 environment variable decides.
The code doesn’t show which phase GitHub has switched on. The changelog does: in Phase 2 the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION variable still returns Node 20, and the entry says that opt-out “is no longer available”, which leaves Phase 3:
Advertisements
// Phase 3: Always use Node 24 regardless of environment variables
if (requireNode24)
{
return (Constants.Runner.NodeMigration.Node24, null);
}C#The handler factory only enters this decision when the action’s metadata says node20. The runner leaves an action that says node24 alone; in the words of the comment, “We don’t modify if node24 was explicitly specified”. Older values get the same treatment: node12 and node16 are rewritten to node20 first, so a node16 action ends up on Node 24 as well.
Each action that was rewritten goes into a list, and at the end of the job the runner prints one warning with that list. The job-summary code words it as “Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24”, followed by the action names and a link to the 2025 deprecation post.
That warning is where a job log shows which of your steps are affected.
One path fails, and it is ARM32 Linux with the kill flag
Only one path in that selection code ends in a failure, and it is Linux on ARM32 with the actions_runner_kill_linux_arm32 flag set: the step fails with “Linux ARM32 runners are no longer supported”. The other ARM32 flag, actions_runner_deprecate_linux_arm32, keeps such actions on Node 20 and warns that the platform is deprecated.
Advertisements
The author of that code, in the pull request merged on March 17, 2026, explained why: “On linux-arm32 we can’t do that since there’s no node24 binary”.
The Node 20 binary itself has not left the runner package. The build script that assembles version 2.338.0 still downloads Node 20.20.2 next to Node 24.21.0, and a linux-armv7l build of Node 20.
The removal is a flag the runner reads from the job, not a file taken out of the package. What the phrase “no longer available” means, in the current code, is that nothing you set in a workflow or on a self-hosted machine can select Node 20 for an action any more.
Advertisements
12 of 46 official action tags still say node20
The changelog says the newest versions are fine. We read the action.yml file at every major tag from v3 of nine actions published by the actions organization, up to the newest major of each, 46 tags in all, and recorded the runs.using value of each.
The figure shows the result per action and tag: the newest major of every action says node24, and the tag where the colour changes is different for each one.

Of the 46 tags, 20 say node24, 12 say node20, 11 say node16 and 3 say node12. The node20 tags are the ones many workflows still pin: actions/checkout@v4, actions/setup-node@v4, actions/cache@v4, actions/upload-artifact@v4 and @v5, actions/download-artifact@v4, @v5 and @v6, actions/github-script@v7, actions/setup-python@v5, actions/setup-go@v5 and actions/setup-java@v4.
Advertisements
A maintainer moves a major tag to the latest patch, so these values are what the tags pointed to on October 13, 2026, UTC.
GitHub’s own ci/node.js.yml template, in the actions/starter-workflows repository, uses actions/checkout@v4 and actions/setup-node@v4. Both say node20. A project that started from that template and never bumped the tags gets the forced-upgrade warning on every run.
Third-party actions are further along, at least the eight we checked. The newest major of docker/build-push-action, docker/setup-buildx-action, docker/login-action, softprops/action-gh-release, pnpm/action-setup, peter-evans/create-pull-request and dorny/paths-filter says node24. The eighth, codecov/codecov-action@v7, is a composite action and declares no Node runtime of its own; one of its steps calls actions/github-script pinned at v8.0.0, a node24 version.
Advertisements
How to find the node20 actions in your own workflows
To see them before a run, read the metadata of each action a workflow references. The script below takes owner/repo@ref lines on standard input, downloads the action.yml at that ref and prints the runs.using value. Save it as actions-node-using.sh and make it executable:
#!/usr/bin/env bash
# For each owner/repo@ref on stdin, fetch action.yml at that ref and print runs.using.
set -u
while read -r ref; do
[ -z "$ref" ] && continue
repo="${ref%%@*}"; tag="${ref#*@}"
using=""
for f in action.yml action.yaml; do
body=$(curl -fsSL "https://raw.githubusercontent.com/$repo/$tag/$f" 2>/dev/null) || continue
using=$(printf '%s\n' "$body" | awk '/^runs:/{r=1;next} r && /^[^[:space:]]/{r=0} r && /^[[:space:]]+using:/{gsub(/[[:space:]]*using:[[:space:]]*/,""); gsub(/["'\''\r]/,""); print; exit}')
break
done
printf '%-45s %s\n' "$ref" "${using:-<no action.yml at ref>}"
doneBashFeed it the uses: lines of a workflow file. The starter workflow comes from the actions/starter-workflows repository:
curl -fsSL -o node.js.yml https://raw.githubusercontent.com/actions/starter-workflows/main/ci/node.js.yml
grep -oE 'uses:[[:space:]]*[^ ]+@[^ ]+' node.js.yml | sed 's/uses:[[:space:]]*//' | sort -u | ./actions-node-using.shBashIt prints one line per action:
actions/checkout@v4 node20
actions/setup-node@v4 node20PlaintextThe script reads only the top-level action.yml; an action referenced with a subdirectory path needs the path added to the URL, and an action that lives on a private repository needs a token. A download that fails for any reason prints the same no action.yml at ref line, and the awk reads the YAML as text, so an unusual layout can slip past it.
It also reports docker and composite. A docker action has no Node runtime. A composite one has none of its own, but its steps can uses: other actions, and a JavaScript action called that way gets the same treatment from the runner; the script doesn’t follow those steps.
Advertisements
Any line that says node20, node16 or node12 is an action the runner is now rewriting, and the fix is a version that declares node24; for the official actions, the first such major is listed below. Tested with: Ubuntu 24.04.2 LTS (WSL2), bash 5.2.21, curl 8.5.0, git 2.43.0, on October 13, 2026, UTC.
The GitHub Actions Node 20 dates moved three times
The deprecation post of September 19, 2025, opened with the reason, “Node20 will reach end-of-life (EOL) in April of 2026”, and a plan: “We plan to migrate all actions to run on Node24 in the fall of 2025.”
The post pointed to runner 2.328.0, released on August 13, as the version with both runtimes, Node 20 as the default and FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true for anyone who wanted to try the new one early. The runner’s own release notes put the first support for running Node 24 actions one version earlier, in 2.327.0 of July 22.
Advertisements
Three editor’s notes on that post record what happened next. Two of them move the date GitHub had set for the switch to Node 24 by default: on February 25, 2026, to June, and on May 19 to June 16, with the opt-out still working after it. The third, on August 25, set the removal of Node 20 for September 23.
In the runner’s own code the removal date had been September 16; the pull request that moved it, merged on September 8, gives the reason: “so customers receive a full four weeks of notice”.
Node 20 itself reached its end date, April 30, 2026, by the Node.js release schedule, almost five months before GitHub stopped using it for actions.
Advertisements
Runner 2.338.0, released on October 6, moved the runner’s internal Node, the one behind hashFiles() and ./run.sh --check, from 20 to 24. A self-hosted runner that needs the old behaviour can set ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION=node20; the release notes say that covers “hashFiles() and --check only”.
What changes for you
If you only use actions, the change is a round of tag bumps, to a version that declares node24. That is not always the next major. For the 12 node20 tags above, the first major that says node24 is v5 for checkout, setup-node, cache and setup-java, v6 for upload-artifact, setup-python and setup-go, v7 for download-artifact and v8 for github-script; upload-artifact@v4 and download-artifact@v4 skip one and two majors of node20 on the way. Each major you skip has its own breaking changes to read, and the job warning tells you which actions you missed.
One reader of the 2025 announcement described on Hacker News pinning actions/checkout@main instead, to stop updating “actions/checkout@v2 to actions/checkout@vwhatever”. The workflow syntax reference shows a branch as one of four ways to reference an action, next to a commit, a major tag and a full version, and warns against it: “if someone releases a new major version with a breaking change, your workflow could break”.
Advertisements
Maintainers of an action have one line to change on GitHub’s side, runs.using: 'node24'; the work is making the code run on Node 24. The metadata reference still listed node20 as a value, “Use node20 for Node.js v20”, on the day we read it. Until you publish, every user of your action sees it in the forced-upgrade warning.
Self-hosted runners, and the next date on the calendar
For self-hosted runners, the platform list is the part to check. macOS 13.4 and earlier can’t run Node 24, and ARM32 Linux has no Node 24 binary; GitHub’s entry says runners on those are “no longer supported”, and it scopes the change to “github.com and GitHub with Data Residency”; it does not mention GitHub Enterprise Server. A commenter who opened with “Ops type here” made the point on Hacker News a year ago: “Node is language the runner client uses”, so the change falls on the people who build runner machines.
The next date on the same calendar is April 30, 2027, when Node 22 reaches its end in the Node.js schedule. Microsoft’s Azure Functions retirement notice of September 22, 2026, uses that same date for its own Node.js 22 support.
Advertisements
Node 24 has an end date of its own
Node 24 goes into maintenance on October 20, 2026, and ends on April 30, 2028, which is the window the current node24 actions have. The last cycle took a year from deprecation post to removal, through a runner release with both runtimes, a default switch and three date changes. The September 23 entry does not start the next one: it names no successor to Node 24. Until a runner release lists a runtime newer than node24, nothing in that cycle has moved.
English 










