---
service: "Publicasta"
schema_version: "1.0"
article_id: 716
title: "GitHub Actions has removed Node 20: what open-source maintainers need to check now"
language: "en"
default_language: "en"
canonical_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=en"
json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=en"
api_url: "https://publicasta.com/api/public/v1/channels/open_source_radar/articles/github_actions_node24_migration_open_source_maintainers?lang=en"
channel_url: "https://publicasta.com/api/public/v1/channels/open_source_radar"
channel_articles: "https://publicasta.com/api/public/v1/channels/open_source_radar/articles"
search_url: "https://publicasta.com/api/public/v1/search"
documentation_url: "https://publicasta.com/api-docs#reading-publicasta"
openapi_url: "https://publicasta.com/api-docs/openapi.json"
published_at: "2026-09-28T07:04:31+00:00"
updated_at: "2026-09-28T07:04:31+00:00"
translations:
  - language: "ar"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=ar"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=ar"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=ar"
  - language: "de"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=de"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=de"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=de"
  - language: "en"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=en"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=en"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=en"
  - language: "es"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=es"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=es"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=es"
  - language: "fr"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=fr"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=fr"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=fr"
  - language: "pl"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=pl"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=pl"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=pl"
  - language: "ru"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=ru"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=ru"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=ru"
  - language: "zh"
    html_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers?lang=zh"
    markdown_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.md?lang=zh"
    json_url: "https://publicasta.com/open_source_radar/github_actions_node24_migration_open_source_maintainers.json?lang=zh"
---

# GitHub Actions has removed Node 20: what open-source maintainers need to check now

> GitHub Actions now runs JavaScript actions on Node 24 after removing Node 20. The migration is mostly a metadata and release problem, but self-hosted runners, macOS versions, ARM32 devices, caches, and local emulators can still turn it into a failed build.

GitHub Actions has crossed the line from warning to breakage: Node 20 is no longer available as the runtime for JavaScript actions on GitHub-hosted runners. Since September 23, 2026, those actions run on Node 24, and the temporary opt-out that allowed repositories to keep using Node 20 has been removed.

 ![Editorial technology illustration of an open-source maintainer reviewing a GitHub Actions Node 20 to Node 24 runtime migration, with CI pipeline artifacts and a self-hosted runner.](https://publicasta.com/storage/projects/10/pages/716/2026/09/eb75953e-6fdf-433e-8e16-e6eabd4850fd.webp)

 For most repositories, the visible fix is simple: update action references and move on. For open-source maintainers, however, the important work is broader. An action can look healthy in its source tree while its published tag still points to an old `dist` bundle. A workflow can use a current JavaScript action and still fail on a self-hosted runner, an older Mac, or an ARM32 board. Local tools that emulate Actions may also continue to execute the wrong runtime and give a false sense of compatibility.

 The practical advice is to treat this as a release and support-matrix audit, not as a one-line replacement of `node20` with `node24`. The runtime change is the immediate event. The more durable issue is how open-source projects communicate supported runners, publish immutable action artifacts, and test the environments their users actually run.

 ## What changed on September 23

 GitHub’s retirement notice says that runners now use Node 24 for JavaScript actions. It also says that `ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION` is no longer available. That variable was a temporary escape hatch during the migration; it is not a supported compatibility mechanism now. GitHub applies the change to github.com and GitHub with Data Residency.

 The distinction between an action’s runtime and the Node version installed for a workflow matters. A JavaScript action declares its execution runtime in `action.yml`, normally under a block resembling this:

 ```yaml
runs:
  using: node24
  main: dist/index.js
```

 That setting controls the Node runtime GitHub uses to launch the action. It is separate from a later step such as `actions/setup-node`, which installs a Node version for commands in the repository’s own build, test, or packaging process. Installing Node 20 with `setup-node` does not make an action that declares `node20` compatible with a runner where the Node 20 action runtime has been removed. Conversely, changing an action’s declared runtime does not automatically change the Node version used by the project’s tests.

 This is why a repository can contain several independent migration surfaces:

 - JavaScript actions declared in the repository’s own `action.yml` files.
- Third-party actions referenced in workflow files.
- The bundled JavaScript under `dist/`, if the action checks compiled output into Git.
- Self-hosted runner versions and operating systems.
- The project’s own Node version used for tests, builds, CLIs, or release scripts.

 GitHub’s earlier deprecation notice gave maintainers a transition period and documented the eventual removal date. Node.js itself lists Node 20 as having reached end of life on March 24, 2026. The Actions change therefore removes an execution path that was already based on an unsupported upstream runtime; it is not a new Node.js release policy.

 ## The first audit: find what will actually execute

 Start with the workflow files, but do not stop there. Search for both direct action usage and action definitions. A useful first pass in a repository is:

 ```text
.github/workflows/
action.yml
action.yaml
*/action.yml
*/action.yaml
```

 In workflow files, look for references such as `uses: owner/project@vN`. A major version tag does not tell you which Node runtime the referenced release uses. The action must be inspected at the resolved tag or commit. This is especially important for small community actions that may have received a source-level update without publishing a new release.

 For an action maintained in the same repository, inspect `action.yml` and identify `runs.using`. If it says `node20`, change it to `node24`, then rebuild the action’s bundled output when the project expects `dist/` to be committed. Many JavaScript actions run compiled files rather than the TypeScript or JavaScript source in the repository. A correct source change with a stale bundle is not a complete release.

 The next question is whether the action’s dependencies support Node 24. Node 24 is not merely a label accepted by the metadata parser. It brings a newer V8 and a newer set of Node APIs, and it can expose assumptions about module loading, package exports, OpenSSL behavior, or filesystem and stream APIs. The risk is not that every Node 20 action will fail. The risk is that a project may never have exercised its actual release artifact under the new runtime.

 For third-party actions, prefer a maintainer-published release that explicitly documents Node 24 support. Do not assume that replacing `@v3` with `@v4` is correct just because the number is higher. Read the release notes, inspect the action metadata, and check whether the new version introduces unrelated behavior changes. A runtime migration is a poor reason to accept a major-version change without reviewing inputs, outputs, permissions, and security behavior.

 ## Why the first-party action updates are useful examples

 GitHub’s first-party actions provide a concrete picture of how the migration is being handled. The current `actions/checkout` changelog records Node 24 updates in its release history, and the current `actions/setup-node` documentation identifies newer action versions that use Node 24. These projects do more than edit one metadata field: they publish a new release, update documentation, run their own test matrix, and call out runner requirements and behavioral changes.

 That pattern is worth copying in smaller repositories. A responsible migration release should answer four questions clearly: which action version contains the change, what runner version is required, which operating systems or architectures are no longer supported, and whether the action’s inputs or outputs changed. The answers belong in the release notes even if the code diff is tiny.

 The `actions/setup-node` project is also a reminder that runtime migration can overlap with other changes. Its current documentation describes a Node 24-based action and includes guidance around package-manager caching. It warns that automatic caching is not always appropriate for workflows with elevated privileges or sensitive credentials. That warning is not caused by Node 24, but it matters during the same audit because maintainers often update action versions and workflow security settings together.

 A user who upgrades an action to obtain Node 24 support may also receive changes to cache defaults, authentication behavior, supported runner versions, or package-manager detection. The right upgrade review is therefore not “does the YAML parse?” but “what code now executes with what permissions and what cached state?”

 ## Self-hosted runners are the sharp edge

 GitHub’s retirement notice calls out two compatibility limits for Node 24: macOS 13.4 and earlier are incompatible, and ARM32 is not officially supported. Those limits are particularly relevant to open-source projects because their contributor and user populations are more varied than the default GitHub-hosted runner matrix suggests. A project may have a green Ubuntu build while users run the action on an older Intel Mac, a Raspberry Pi-class ARM32 device, or a private runner behind a firewall.

 The operating-system problem is easy to miss when the workflow uses a broad label such as `macos-latest`. GitHub-hosted labels move over time, while a self-hosted label usually describes a machine that an administrator must upgrade manually. A repository that supports self-hosted runners should document a minimum runner version and supported operating-system range rather than treating GitHub’s hosted images as the complete support policy.

 The architecture problem is even less visible. ARM32 machines are uncommon in hosted CI, so a default matrix may never exercise them. If the project has users running Actions locally or on small edge devices, the maintainers need to decide whether ARM32 is still supported. That decision should be explicit. “The action runs on Linux” is not sufficiently precise when the runtime’s architecture support differs by platform.

 Self-hosted administrators should update the Actions runner software before diagnosing action failures. Some Node 24-compatible action releases require a sufficiently recent runner, and the action’s release documentation should be treated as the authority for the minimum version. Updating the runner does not make an incompatible operating system compatible, but an old runner can produce misleading errors before the action code is even reached.

 The safe sequence is to stage the runner upgrade, run a representative workflow with secrets removed or replaced by test credentials, and then test the workflows that use deployment, publishing, or release permissions. A basic unit-test job is not enough if the action also uploads artifacts, signs packages, opens pull requests, or exchanges an OIDC token.

 ## The published artifact is part of the software

 JavaScript Actions often commit their compiled `dist` directory to Git so that users do not need to install dependencies or build the action during a workflow run. This creates a release trap: the repository can show a modern source file while the tag consumed by users still contains a stale bundle.

 Maintainers should check the exact artifact that the action will execute at the release ref. That means confirming all of the following:

 1. `action.yml` or `action.yaml` declares `node24`.
2. The `main` or `pre` path points to the expected generated file.
3. The generated file exists in the published tag.
4. The release was built from the intended commit.
5. The release notes identify the tag or commit users should select.
6. The test workflow exercises the bundled artifact, not only the source through a development shortcut.

 Projects that use a release bot or a build workflow should inspect whether the workflow itself depends on an old action. A migration can fail in a circular way: the maintainer updates the action, but the release workflow still invokes an obsolete checkout, setup, packaging, or publishing action. Upgrade the CI used to produce the artifact before relying on that CI to prove the artifact is current.

 Pinning matters here too. A mutable major tag is convenient for users, but a security-sensitive workflow may pin an action to a full commit SHA and update it through a reviewed process. If a project publishes both a human-friendly tag and a signed or digest-pinned reference, its release notes should make the relationship clear. Runtime migration is a good opportunity to remove ambiguous references and document why a particular ref is trusted.

 ## Node 24 support does not mean the project should run everything on Node 24

 There are two different compatibility questions in a repository that contains an Action. The first is whether the Action itself can be launched by GitHub’s Node 24 runtime. The second is whether the project’s own application, library, test suite, or command-line tool supports Node 24. They may have different support policies.

 An action can run on Node 24 while invoking a project that still supports Node 18, 20, and 22. The action’s runtime is an implementation detail of the automation platform; the runtime used to test the software may be a public compatibility promise. Maintainers should not silently raise the project’s minimum Node version merely because GitHub changed the Action runtime.

 Conversely, a project that uses Node 24-specific APIs in its action implementation should say so in the contributor documentation. Developers who run tests locally may otherwise use Node 20 and fail only when the bundled action executes on GitHub. A small runtime matrix can prevent this mismatch:

 ```yaml
strategy:
  matrix:
    node: [22, 24]
steps:
  - uses: actions/checkout@v7
  - uses: actions/setup-node@v7
    with:
      node-version: ${{ matrix.node }}
  - run: npm ci
  - run: npm test
```

 That matrix is an example, not a universal recommendation. The versions should match the project’s stated support range. If the action itself is tested as a packaged artifact, the workflow should also invoke it through the same path that users invoke, rather than only importing its source modules.

 For projects that publish npm packages, the migration is also a chance to review package metadata. `engines.node` should describe the supported application or library range, not simply mirror the Action runtime. Lockfiles should be committed, and dependency updates should be reviewed separately from the runtime migration so that a failure can be attributed to the right change.

 ## Local emulators can hide the problem

 Tools that run GitHub Actions workflows locally are useful, but they do not automatically reproduce GitHub-hosted runner behavior. A local emulator may select a container image with Node 16 or Node 20, may not implement the same action runtime mapping, or may use a different runner version. A green local run therefore does not prove that the published action works on GitHub’s Node 24 runtime.

 One issue in the `actions/checkout` project illustrates the confusion: a local execution tool can report an old container image even while the action release uses Node 24. The problem is not necessarily in the action; it can be in the emulator’s runtime model. Maintainers should record which parts of the workflow are validated locally and which require a real GitHub-hosted or correctly configured self-hosted runner.

 A practical test plan uses both environments deliberately. Run fast unit and integration tests locally, then run a minimal real workflow against the action’s packaged release. The real workflow should cover the action’s important inputs, including private repositories, large files, proxy settings, credentials, pull-request events, or generated outputs where applicable. Local testing remains valuable for iteration, but it should not be presented as a substitute for platform-level validation.

 ## What users should change in their workflows

 Users do not normally need to rewrite every workflow step. The priority is to update action references to releases that support Node 24, then inspect any failures that remain. Start with the actions most central to the workflow: checkout, setup-node, cache, artifact upload and download, language setup, container operations, and publishing actions.

 A safe upgrade checklist looks like this:

 - Read the action’s release notes and confirm Node 24 support.
- Check the action’s documented minimum runner version.
- Review permissions, secrets, cache settings, and authentication changes.
- Test pull requests from forks separately from internal pushes.
- Test the workflow on every operating system and architecture you actually support.
- Keep the project’s own `node-version` or version file explicit.
- Re-run release and publishing jobs with a dry-run or staging destination.
- Remove obsolete Node 20 opt-out variables; they no longer provide a fallback.

 Do not confuse `actions/setup-node` with the runtime used by `uses:` actions. This workflow installs Node 24 for shell commands:

 ```yaml
- uses: actions/setup-node@v7
  with:
    node-version: 24
- run: npm test
```

 It does not repair a separate third-party action whose metadata still says `using: node20`. That action must be updated by its maintainer, or replaced with a maintained alternative. If the action is abandoned and the project cannot accept the risk of running unreviewed code, a small local script may be safer than adopting an unverified fork—but that choice should be made with the action’s permissions and data flow in mind.

 Users should also resist changing all action versions at once. Updating checkout, setup-node, cache, artifact handling, and a deployment action in one commit makes failures difficult to isolate. A staged series of small pull requests produces a clearer audit trail and makes rollback possible. For a community project, that clarity is useful to downstream packagers and contributors who maintain older branches.

 ## What maintainers should put in the release notes

 A release note saying “migrate to Node 24” is better than silence, but users need operational detail. The note should state whether the change affects the action runtime, the project runtime, or both. It should name the first release containing the change and explain whether users need to change their workflow references.

 It should also identify known limits. For example, the GitHub notice makes clear that macOS 13.4 and earlier and ARM32 are no longer supported for Node 24-based JavaScript actions. If the project has a separate restriction—such as a native dependency, a minimum runner version, or a requirement for a newer npm release—that belongs in the same release note.

 Maintainers should document how to verify the migration. A short command to inspect the action metadata, a link to the CI matrix, and a statement that the bundled `dist` artifact was tested are more useful than a long changelog of dependency bumps. If the project has a stable branch that will not receive the migration, say so plainly and provide the last supported action release.

 This is also a useful moment to publish a support policy. Open-source users often infer support from whatever happens to pass in public CI. A table or paragraph that separates GitHub-hosted runners, self-hosted runners, local emulators, operating systems, and architectures prevents that accidental promise. It helps users decide whether an upgrade is appropriate before their deployment pipeline is the thing that discovers the boundary.

 ## Security and supply-chain implications

 The Node 20 retirement is a compatibility event, but action upgrades are supply-chain changes. An action runs code in a workflow context that may contain repository contents, tokens, cloud credentials, signing material, or access to deployment systems. A new release should receive the same review as any other dependency that executes with those privileges.

 Review the diff between the old and new action refs, not only the release headline. Check changes to permissions, shell commands, network endpoints, artifact handling, and post-job cleanup. For actions that process untrusted pull requests, verify whether the new release changes when code is checked out or when credentials become available. The runtime migration should not be used as a reason to waive those checks.

 Caching deserves particular attention. A cache can improve performance, but a workflow that restores dependency data before handling untrusted input may create a poisoning or credential-exposure path. The current `setup-node` documentation recommends disabling automatic package-manager caching when it is not needed for workflows with elevated privileges or sensitive information. That recommendation is broader than Node 24, but it is directly relevant when action upgrades cause cache behavior to change.

 Pinning action references to reviewed commit SHAs can reduce the risk of an unexpected tag movement, although it adds maintenance work. Projects that use Dependabot, Renovate, or another update mechanism should make sure the tool understands the action’s runtime migration and does not keep reopening the same obsolete major version. A signed release, provenance metadata, and a reproducible build are useful additions, but none replaces review of the code that will run with secrets.

 ## Alternatives when an action has not migrated

 There is no universal substitute for an unmaintained action. The right choice depends on what it does, the permissions it needs, and whether its behavior is essential to the project. The options usually fall into four categories.

 First, ask the maintainer for a Node 24 release and contribute the migration if the project is active and the change is straightforward. A pull request should include the built artifact, tests against the supported runner matrix, and release documentation. Updating only `runs.using` without testing the bundle is incomplete.

 Second, replace the action with a maintained project that has a clear release and support policy. Compare behavior and permissions, not only feature names. An action with fewer stars but transparent maintenance and a narrow permission model may be a better fit than a popular action whose current release process is opaque.

 Third, move a small piece of logic into ordinary workflow steps or a repository script. This can reduce dependence on an action runtime, but it does not make the code automatically safer. Shell scripts still run with workflow permissions and must validate inputs, quote data correctly, and avoid exposing secrets.

 Fourth, isolate the legacy action in a dedicated job or repository while a migration is prepared. This is a containment strategy, not a permanent solution. Limit its permissions, avoid passing deployment credentials, and make its outputs explicit. The project should state the temporary nature of the arrangement so downstream users do not mistake it for support.

 A fork can be appropriate when the original project is abandoned and the code is small enough to audit. It can also create long-term maintenance debt. The fork should document its relationship to the upstream action, preserve license notices, publish its own releases, and explain how users can verify the generated artifact. A renamed tag without a release process merely moves the uncertainty.

 ## The larger lesson for open-source automation

 Platform-managed runtimes are a reminder that an open-source action has two maintenance contracts. The first is with the source ecosystem: dependencies, compilers, package managers, and language releases. The second is with the automation platform: runner versions, supported operating systems, execution semantics, permissions, and artifact conventions. A project can satisfy one contract and fail the other.

 The Node 24 migration also exposes a recurring weakness in open-source infrastructure: users often discover support policy through a failed job. That is avoidable. Repositories can publish a tested matrix, keep the release artifact visible, state minimum runner versions, and add a scheduled job that tests upcoming runtime changes before the platform makes them mandatory.

 The action runtime should be tested as a product surface. It deserves release notes, compatibility tests, a security review, and a rollback story. The source code is only one part of what users install; the metadata, generated bundle, tag, runner, and permissions form the actual software package.

 For users, the immediate job is to update supported action releases and verify the environments that matter. For maintainers, the more important job is to make the next migration boring. That means a published support policy, explicit runner requirements, a reproducible release artifact, and CI that tests the path users execute. Node 24 is the new baseline on GitHub Actions. The quality of the migration will be measured less by whether a YAML file changed and more by whether contributors can understand the new boundary before it reaches production.

 ## Sources and further reading

 - [GitHub: Node 20 is no longer available in GitHub Actions](https://github.blog/changelog/2026-09-23-node-20-is-no-longer-available-in-github-actions/) — the retirement notice, Node 24 default, removed opt-out, and affected macOS and ARM32 environments.
- [GitHub: Deprecation of Node 20 on GitHub Actions runners](https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/) — the migration timeline and earlier guidance for maintainers, users, and self-hosted runner administrators.
- [Node.js release schedule](https://nodejs.org/en/about/previous-releases) — Node 20’s end-of-life date and the support status of Node 22 and Node 24.
- [GitHub Actions metadata syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax) — the `runs.using` declaration for JavaScript actions.
- [actions/checkout changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) — a first-party example of releases and documentation updates around Node 24.
- [actions/setup-node repository](https://github.com/actions/setup-node) — current usage, supported Node version syntax, caching guidance, and runner requirements.
- [actions/runner repository](https://github.com/actions/runner) — the open-source runner implementation and its Node upgrade automation.
- [GitHub Changelog: Actions](https://github.blog/changelog/label/actions/) — related changes to the Actions platform and runner ecosystem.
