🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Fixes
Today I Fixed

GitHub Actions Failed After Node 20 Removal: Node 24 Runner Fix

GitHub ActionsSep 28, 202615 minutes to fixgithub-actionsnodejscicdtroubleshooting

Problem

A workflow that had run successfully began failing in JavaScript-based action steps after September 23, 2026. Re-adding this environment variable did not help:

yaml
env:
  ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: "true"

Application commands were not the first failure. Checkout, setup, cache, or another uses: step failed before the main build ran.

Root Cause

GitHub removed Node 20 from Actions runners on September 23, 2026. JavaScript actions now run with Node 24, and the temporary Node 20 opt-out is no longer available.

The workflow had two stale dependencies:

  • an old action release built for the retired runtime;
  • a self-hosted runner on a host that was not compatible with Node 24.

Node 24 is incompatible with macOS 13.4 and earlier and does not officially support ARM32. Installing a different Node version with actions/setup-node does not change the runner's internal JavaScript-action runtime.

Confirm Which Action Fails

Expand the first failed uses: step in the job log. Then inventory every action reference:

bash
rg -n "uses:" .github/workflows .github/actions

For a local JavaScript action, inspect its metadata:

bash
rg -n "using:|main:|post:" .github/actions/**/action.y*ml

For self-hosted runners, record the runner version, operating system release, and architecture. A workflow label such as self-hosted does not prove the host supports Node 24.

Fix

First, upgrade external actions to reviewed releases that support Node 24:

yaml
steps:
  - uses: actions/checkout@v5
  - uses: actions/setup-node@v5
    with:
      node-version: 22
      cache: npm

The application can still use Node 22 in this example. Node 24 is the runtime for the action implementation, not automatically the application.

For an internal JavaScript action, update action.yml and rebuild its distributable bundle:

yaml
runs:
  using: node24
  main: dist/index.js
bash
npm ci
npm test
npm run build
git diff --check

Next, update the self-hosted runner using GitHub's supported runner upgrade process. If the host is macOS 13.4 or older or ARM32, move the workload to a supported operating system and architecture. Do not copy Node 20 back into the runner directory as a permanent workaround.

Finally, delete the obsolete opt-out:

yaml
# Remove this; it stopped working after Node 20 removal.
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: "true"

Verify the Entire Job

Run the workflow on a non-production branch and check more than the main build step:

  1. checkout and setup steps complete;
  2. cache restore and cache save both work;
  3. artifacts upload successfully;
  4. authentication cleanup and other post-job hooks complete;
  5. the same workflow works on each self-hosted runner group;
  6. no logs still mention the retired Node 20 opt-out.

If a third-party action has no Node 24-compatible release, replace it with a maintained alternative, fork it under your organization's review process, or temporarily implement the required operation with a well-scoped shell step. Do not switch to an unreviewed branch merely to make the run green.

Lesson

actions/setup-node controls the Node version used by your project. The runner controls the Node version used to execute JavaScript actions. When a uses: step fails after a runner runtime retirement, update the action and runner platform first.

For the complete inventory and rollout process, see the GitHub Actions Node 24 migration guide.

Sources

Did this fix work?

Tell us what needs improving. No account required.