GitHub Actions Node 24 Migration Guide for CI Teams

GitHub Actions no longer provides Node 20 for JavaScript actions. As of 23 September 2026, runners use Node 24, and GitHub has removed the temporary ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION opt-out. A workflow that depends on an old JavaScript action now needs a compatible action release or a maintained replacement.
The migration has two owners. Action maintainers must publish releases whose metadata names node24 and whose bundled code works on that runtime. Workflow owners must find every uses: reference, upgrade third-party actions, and verify self-hosted runner operating systems and architectures. Updating the Node version used by ordinary shell steps does not complete either job.
Understand Which Node Runtime Changed
GitHub Actions can involve several Node installations in one job. Mixing them up leads to false confidence.
JavaScript actions use the runner runtime
A JavaScript action declares its runtime in action.yml or action.yaml:
name: Example action
description: Demonstrate the Node 24 action runtime
runs:
using: node24
main: dist/index.js
The runner launches dist/index.js with the declared embedded runtime. The workflow consumer does not normally control that value.
Run steps use the job environment
A workflow may install a Node version for scripts:
- uses: actions/setup-node@v5
with:
node-version: 24
- run: npm ci
- run: npm test
This affects the run: steps after setup. It does not override the runtime inside another repository's JavaScript action. An old action can fail under its own runtime even when node --version in a later step reports 24.
Composite and container actions differ
Composite actions execute a sequence of workflow-style steps and declare runs.using: composite. Docker container actions execute inside their container image and declare runs.using: docker. They do not migrate by changing their metadata to node24 unless they also contain or invoke a separate JavaScript action.
Audit by action type. Searching only for node20 inside your own workflows will miss remote JavaScript actions whose metadata lives elsewhere.
What the Final Removal Means
GitHub's final notice is short and unambiguous. Node 20 is unavailable on Actions runners, Node 24 is the JavaScript-action runtime, and the opt-out is gone.
The compatibility flag cannot buy more time
During earlier phases, organizations could set ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION as a temporary escape hatch. GitHub says that option is no longer available. Do not add it to repository, organization, runner, or environment configuration expecting a rollback.
If a dependency still requires Node 20, the practical choices are to upgrade it, patch and publish a maintained fork, replace the action, or run equivalent logic as an explicitly managed script or container.
First-party actions have compatible releases
GitHub says the newest versions of all first-party actions support Node 24. That does not mean every historical major or commit pin has changed. Repositories using old tags or SHAs still need an inventory and version update.
Read each action's release notes before changing a major version. The runtime migration may arrive alongside input changes, permission requirements, cache behavior, or dependency upgrades.
The change applies to hosted and self-hosted execution
The announcement covers github.com and GitHub with Data Residency. Hosted runners receive the platform-managed runtime. Self-hosted runners depend on a supported host, architecture, and current runner application.
GitHub specifically calls out macOS 13.4 and earlier and ARM32 as unsupported for Node 24 actions. Those machines need an operating-system or hardware migration, not a workflow flag.
Build a Complete Action Inventory
Start by locating every action reference and every locally maintained action. The inventory should include reusable workflows because an action can be several layers away from the repository that triggers it.
Search workflow files
rg -n --glob '*.yml' --glob '*.yaml' 'uses:' .github
rg -n --glob 'action.yml' --glob 'action.yaml' 'using:\s*["'"']?node(16|20)' .
rg -n 'ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION' .github
Classify each uses: entry as GitHub first party, third party, local action, reusable workflow, composite action, or container action. Record the current tag or SHA and the proposed compatible release.
Expand reusable workflows
A repository may call org/automation/.github/workflows/build.yml@v3, which then calls six actions. Search the automation repository and any centrally managed workflow catalog as well as application repositories.
Record the full call chain for release and deployment jobs. When a nested workflow changes its action versions, the application repository may show no YAML diff even though its execution environment has changed. That relationship belongs in the migration evidence and rollback plan.
Create one organization-level list. Fixing the shared workflow once may repair hundreds of consumers, while upgrading each consumer independently can create inconsistent behavior.
Inspect dynamic generation
Some organizations generate workflow YAML from templates or infrastructure code. Search the source templates and generated files. Update the generator first, then regenerate and review the diff.
Also check documentation and starter repositories. A copied workflow can reintroduce an obsolete action months after the main migration finishes.
Prioritize by Blast Radius
Not every action deserves the same migration order. Start where failure blocks delivery or where broad privileges increase risk.
Fix central workflows first
Organization-required workflows, release pipelines, security scans, artifact publishing, and deployment gates should move before low-frequency utility jobs. Their action versions propagate widely and their failures affect more teams.
Create canary repositories for shared workflows. A central change should run against representative Node, Python, container, and monorepo projects before the new ref becomes the default.
Identify privileged actions
Actions that receive contents: write, id-token: write, package publishing, deployment environments, secrets, or cloud credentials need deeper review. A hurried fork of a privileged action can create more risk than waiting for an upstream release.
Prefer upstream releases with clear provenance. If you must fork, remove unnecessary features, pin dependencies, and publish under organization control with a documented retirement plan.
Find unsupported runner pools
Map workflow labels to physical self-hosted pools. A label such as macos-build may hide Intel machines on macOS 13, Apple Silicon machines on a newer release, or a mixture.
Do not rely on the label name. Query runner inventory or run a diagnostic job that records operating-system version, architecture, and runner application version.
Migrate a JavaScript Action
Action maintainers own the metadata, source dependencies, generated bundle, tests, and release tags.
Change the metadata
Update every JavaScript entry point in the action metadata:
runs:
using: node24
main: dist/index.js
pre: dist/setup.js
pre-if: runner.os == 'linux'
post: dist/cleanup.js
post-if: always()
The runtime named in using executes pre, main, and post. Test all three paths, including failures that trigger cleanup.
Test the source on Node 24
Declare the supported engine so local development and CI agree:
{
"engines": {
"node": ">=24"
},
"scripts": {
"build": "ncc build src/index.ts -o dist",
"test": "node --test",
"check-dist": "git diff --exit-code -- dist"
}
}
Run unit tests on Node 24 and include the runner operating systems the action claims to support. Pure JavaScript is the safest path for compatibility across Ubuntu, Windows, and macOS hosted runners.
Rebuild the distribution bundle
Most JavaScript actions commit a bundled dist file because workflow runners do not install the action's development dependencies. Updating package.json or TypeScript source without rebuilding dist leaves consumers on the old code.
npm ci
npm test
npm run build
git status --short
npm run check-dist
Review the generated diff. Confirm it contains the intended source and dependency updates, no local paths, and no secret material.
Audit Dependencies for Node 24
Changing runs.using only selects the runtime. The bundled dependency graph must also work with Node 24.
Update the Actions toolkit
Review the current supported releases of @actions/core, @actions/github, @actions/exec, @actions/cache, and other toolkit packages you use. Read release notes and migration guidance instead of upgrading everything blindly.
Remove dependencies that duplicate platform APIs or are no longer maintained. A smaller bundle reduces supply-chain surface and makes runtime debugging easier.
Check native binaries
GitHub recommends pure JavaScript for actions intended to work across hosted runner platforms. Native Node addons and downloaded binaries introduce architecture, libc, and operating-system compatibility requirements beyond the Node runtime.
If the action must execute a binary, verify its checksums and provide builds for every supported runner. Fail with a clear architecture message instead of downloading an approximate match.
Watch removed and changed APIs
Node 24 includes runtime changes accumulated since Node 20. Run tests with warnings visible and exercise network, TLS, filesystem, streams, child processes, and module loading paths used by the action.
Avoid assuming that a clean TypeScript build proves runtime compatibility. The checked-in bundle and runner environment are the artifacts that matter.
Test the Packaged Action
Unit tests cover source logic. A migration also needs workflow tests that execute the published shape of the action.
Use a fixture workflow
name: action-smoke-test
on:
pull_request:
permissions:
contents: read
jobs:
smoke:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: ./
with:
example-input: fixture
Test the local action path and a packed fixture if your build process changes file layout. Add assertions for outputs, annotations, created files, and cleanup behavior.
Test failure and cancellation
Actions often behave differently when a prior step fails or a job is cancelled. Exercise post hooks, state transfer, temporary files, and process termination.
A cleanup action that fails only on Windows or only after cancellation can leak credentials or leave a self-hosted runner dirty for the next job.
Compare logs and timings
Capture a known Node 20 release's functional output in a safe test environment and compare it with the Node 24 candidate. Ignore expected runner boilerplate changes and focus on action outputs, API calls, cache keys, artifacts, and annotations.
Large timing changes may reveal a dependency behavior change or repeated download. Investigate before moving a widely used tag.
Publish a Safe Release
Workflow consumers need an immutable release they can review and pin.
Create a new release tag
Do not rewrite an existing immutable version to point at different bundled code. Publish a new semantic version. If the action's documented contract remains compatible, the runtime migration can be a minor or patch release according to your policy; if dependencies or inputs change, use the appropriate major release.
Sign the release where your organization supports it and attach provenance for generated artifacts. The commit containing action.yml and dist should be the commit the release tag references.
Move major tags after canaries pass
Many consumers use a floating major such as owner/action@v4. Update the immutable release first, run canaries against it, then move v4 to the tested commit.
Publish the exact commit SHA in the release notes so security-conscious consumers can pin it without resolving a moving tag themselves.
Communicate runner exclusions
State that Node 24 actions do not support macOS 13.4 and earlier or ARM32 runners, following GitHub's notice. If your action has stricter requirements, list them plainly.
Include a replacement path for unsupported users. A container action may be possible on Linux, while older macOS hardware usually needs a runner migration.
Upgrade Third-Party Actions as a Consumer
Workflow owners should prefer a maintained upstream release that explicitly supports Node 24.
Read the target release notes
Check inputs, outputs, permission requirements, default behavior, cache formats, and operating-system support. A jump from an old major version may contain years of changes beyond the runtime.
Update one action family at a time for important workflows. Smaller diffs make failures attributable and rollback safer.
Pin reviewed commits
For third-party actions, a full commit SHA prevents a tag from moving after review:
- uses: owner/action@4f2c9f8d6a0d4b2c2e9b2af07c0c1dd112233445
Add a comment with the human-readable release version. Dependency automation can propose later SHAs, but a reviewer should confirm the referenced commit belongs to the expected upstream release.
Replace abandoned actions
An action that has not published a Node 24 release may be unmaintained. Evaluate whether a first-party action, a short shell script, a composite action, or an internally maintained implementation can replace it.
Keep scripts small and explicit. Reimplementing a mature authentication or deployment action without understanding its security behavior is not a safe shortcut.
Upgrade Self-Hosted Runners
Self-hosted runners add host lifecycle to the migration. The runner application carries the JavaScript action runtime and depends on the host operating system and architecture.
Inventory operating system and architecture
Record runner.os, runner.arch, detailed operating-system version, image revision, and runner application version. GitHub says macOS 13.4 and earlier and ARM32 are unsupported for Node 24.
Move affected jobs to newer machines or supported architectures. Do not hide an unsupported host behind a generic label and hope a setup action repairs it.
Keep the runner application current
GitHub documents that self-hosted runners automatically update when assigned a job or within a week when idle, unless updates are disabled. Managed images and restricted networks can interrupt this process.
Monitor runner versions and connectivity. Replace pets with repeatable images where possible so a failed upgrade does not strand a unique machine.
Rebuild ephemeral images
If runners come from VM or container images, update the base operating system, runner binary, CA certificates, Git, and any native tools used by actions. Test image startup and registration before draining the old pool.
Roll out a small canary pool under a distinct label. Route representative workflows to it, then expand after observing job success, duration, and cleanup.
Preserve Security During the Migration
Runtime urgency should not weaken action supply-chain controls.
Review permissions with every version change
Use the narrowest workflow and job permissions: block. An upgraded action may request capabilities the old workflow inherited implicitly.
For pull requests from forks, keep secrets and write tokens away from untrusted code. The runtime version does not change the risks of pull_request_target, checked-out attacker-controlled code, or command construction from event fields.
Keep workflow changes reviewable
Separate mechanical action-version updates from unrelated refactors. Generate an inventory, link every change to an upstream release, and preserve commit-pin comments.
GitHub's workflow execution protections provide another layer for controlling what can run. See GitHub Actions Workflow Execution Protections for the broader governance model.
Verify generated bundles
Action maintainers should use reproducible or at least reviewable builds. CI can rebuild dist from the locked dependency graph and fail if the committed bundle differs.
This catches a forgotten build and makes it harder to smuggle code into the distributed artifact without changing source or lockfiles.
Roll Out with Canaries
An organization-wide search-and-replace is fast until a central release pipeline breaks. Stage the migration.
Phase one: discovery
Produce the complete inventory, flag unsupported runner pools, and identify actions without compatible releases. Block new Node 20 action metadata through repository policy or a scheduled scanner.
Publish a dashboard by repository and workflow owner. Unknown ownership is itself a migration risk.
Phase two: maintainers and shared workflows
Release Node 24 versions of internal actions and update centrally managed reusable workflows. Run them against canary repositories and supported hosted runner operating systems.
Do not move floating major tags until immutable releases pass the matrix.
Phase three: consumers and runners
Update application repositories in batches. Drain or upgrade unsupported self-hosted pools and watch queue time so label changes do not leave jobs waiting for nonexistent runners.
Track failure rate, duration, cache hit rate, and runner utilization before and after each batch.
Keep batches small enough to identify a common failing action quickly. If twenty unrelated repositories fail after the same shared workflow update, pause the rollout and repair the shared layer instead of asking every application team to invent a local workaround.
Diagnose Common Failures
The error message often points to the layer that still needs work.
The workflow still names an old action
If logs mention an unsupported Node runtime, inspect the resolved action version. A nested reusable workflow or transitive action reference may still point to an old release.
Search the called repository and confirm the tag or SHA's action.yml, not the default branch's metadata.
The source changed but the bundle did not
If behavior looks unchanged, inspect dist. JavaScript action consumers execute the bundled entry point declared by main, not TypeScript source under src.
Rebuild from a clean install, commit the result, and add a CI diff check to stop recurrence.
Hosted runners pass and self-hosted runners fail
Compare operating-system version, architecture, runner application version, network policy, filesystem permissions, and installed native binaries. Node 24 compatibility on the host is only one part of parity.
Use a canary self-hosted label to isolate image changes. Avoid debugging on the only production runner for a release workflow.
setup-node looks correct but the action fails
Remember the runtime split. setup-node controls later shell commands. The failing action's own metadata controls its JavaScript runtime.
Upgrade the uses: reference or fix the action release instead of changing the job's toolchain repeatedly.
Build an Ongoing Guardrail
This migration will not be the last embedded runtime change. Turn the one-time audit into a maintained control.
Scan metadata and workflow references
Run a scheduled job that finds unsupported runs.using values in organization-owned actions and reports action versions that have known migration notices. Feed results to repository owners with clear upgrade links.
Also scan templates and starter repositories so new projects do not copy old pins.
Record action ownership
Each internal action and reusable workflow should have an owner, support window, release process, and deprecation path. Central tooling without ownership becomes an organization-wide blocker when the platform runtime moves.
Keep supported Node and runner versions in the repository documentation and release notes.
Test ahead of platform deadlines
Add the next Node runtime to the action's CI matrix when GitHub announces support, even before it becomes the runner default. Fix deprecations while the old release still works.
The broader Node upgrade considerations in Node.js 24 LTS vs 26 Current help with application runtimes. Keep that decision separate from the embedded Actions runtime covered here.
Migration Checklist
For action maintainers:
- Update
runs.usingtonode24. - Test source and packaged entry points with Node 24.
- Review toolkit and transitive dependencies.
- Rebuild and inspect committed distribution files.
- Test
pre,main,post, failure, and cancellation paths. - Run hosted and supported self-hosted matrices.
- Publish an immutable release and documented commit SHA.
- Move floating major tags only after canaries pass.
For workflow owners:
- Inventory direct, local, reusable, and generated action references.
- Remove the obsolete compatibility flag from configuration.
- Upgrade first-party actions to current supported releases.
- Upgrade or replace third-party actions without Node 24 support.
- Review permissions and pin reviewed third-party commits.
- Upgrade macOS 13.4-and-earlier and ARM32 runner pools.
- Canary central workflows before organization-wide rollout.
- Monitor failures, duration, caches, and runner queues.
The fastest reliable migration starts with ownership. Maintainers publish tested Node 24 artifacts; consumers update explicit references; platform teams replace unsupported runners. Once those responsibilities are separate, most failures become straightforward to locate and fix.
Official Sources
- GitHub changelog: Node 20 is no longer available in GitHub Actions
- GitHub Actions metadata syntax
- Creating a JavaScript action
- GitHub changelog: deprecation of Node 20 on Actions runners
- Workflow syntax for GitHub Actions
- Self-hosted runners reference
- Supported architectures for self-hosted runners
- Secure use reference for GitHub Actions
- GitHub Actions runner releases
- Node.js 24 release information
FAQs
Is Node 20 still available for GitHub Actions?
No. GitHub's final notice says runners now use Node 24 for JavaScript actions and the temporary ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION opt-out is no longer available.
How do action maintainers migrate to Node 24?
Set runs.using to node24 in action.yml or action.yaml, test with Node 24, rebuild the checked-in distribution bundle, publish a new immutable release, and update the moving major tag only after verification.
Do workflow users need to edit runs.using?
Usually not. Workflow users should upgrade each uses reference to an action release that supports Node 24. The action maintainer controls runs.using in that action's metadata.
Which self-hosted runners are incompatible with Node 24 actions?
GitHub says Node 24 is incompatible with macOS 13.4 and earlier and does not officially support ARM32. Self-hosted runners on those operating systems or architectures are unsupported.
Does setup-node choose the runtime for a JavaScript action?
No. actions/setup-node configures Node for run steps in the job. A JavaScript action executes with the runtime named in its own action metadata and supplied by the runner.
Should repositories pin actions to commit SHAs during migration?
Pinning third-party actions to reviewed commit SHAs reduces tag-movement risk. Teams must still update the pinned SHA to a release whose metadata and bundle support Node 24.
How should teams roll back a Node 24 action release?
Keep the previous release immutable, publish fixes under a new tag, and move a major tag only after tests pass. Workflow users can temporarily pin a known-good commit while the action maintainer fixes compatibility.
Work with us
Let's build something together
We build fast, modern websites and applications using Next.js, React, WordPress, Rust, and more. If you have a project in mind or just want to talk through an idea, we'd love to hear from you.
Related Articles
Engineering • 20 min
Cloudflare Worker Previews for Safer Coding Agents
A practical guide to Cloudflare Worker Previews for coding agents, including CI setup, data isolation, access controls, testing, observability, and cleanup.
9/23/2026
Engineering • 35 min
Node.js 24.21 LTS vs 26.8.2 Current: A Production Upgrade Guide
Compare Node.js 24.21 LTS and 26.8.2 Current across compatibility, TLS, HTTP clients, native modules, CI, containers, and rollback.
9/16/2026
Engineering • 8 min
Agentic CI Pipelines: Autonomous Code Review & Testing Tutorial
Learn to build agentic CI pipelines that autonomously review code, generate tests, and self-heal. Replace static automation with AI agents for faster, reliable deployments.
5/4/2026