GitHub Actions workflows can depend on Node.js even when the application itself is not written in JavaScript or TypeScript.

A .NET, Java, Python, Go, or C++ repository can use JavaScript-based GitHub Actions internally. Those actions may depend on a particular Node.js runtime, which means a workflow can have a Node dependency that is easy to miss during normal application development.

This becomes important when GitHub changes the Node.js runtime used by its action infrastructure or when an action version requires a newer runtime.

The first step in handling such a change is finding where Node 20 is actually being used.

This article shows how to identify Node 20 dependencies in GitHub Actions workflows, distinguish action runtimes from application runtimes, troubleshoot old actions, and build a practical migration checklist.

Why Node 20 Dependencies Can Be Hard to Find

A simple search for node-version: 20 is not enough.

Consider this workflow:

name: Build

on:
  push:
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.x'

      - run: dotnet restore
      - run: dotnet test

There is no explicit Node.js version in the YAML.

However, some of the actions used by the workflow are JavaScript actions and have their own runtime requirements.

This creates two different types of Node dependency:

Node.js used by application commands
              +
Node.js used internally by GitHub Actions

These dependencies need to be investigated separately.

The Three Places to Look

When searching for Node 20, check at least these areas:

  1. GitHub Actions workflow files

  2. Action implementations and versions

  3. Application and build configuration

Your repository may contain:

.github/workflows/
package.json
.nvmrc
.tool-versions
Dockerfile
docker-compose.yml
build scripts

A complete search should cover all of them.

Step 1 - Find All Workflow Files

GitHub Actions workflows are normally stored in:

.github/workflows/

A repository may contain several files:

.github/
└── workflows/
    ├── build.yml
    ├── test.yml
    ├── release.yml
    ├── deployment.yml
    └── security.yml

Do not inspect only the main build workflow.

A deployment or release workflow may contain an older action that is not used during normal development.

On Linux or macOS, you can list workflow files with:

find .github/workflows -type f

On PowerShell:

Get-ChildItem .github/workflows -Recurse

Step 2 - Search for Explicit Node Versions

Start with the obvious references.

On Linux or macOS:

grep -Rni "node-version" .github/workflows

You can also search for:

grep -Rni "node20" .github/workflows

On PowerShell:

Get-ChildItem .github/workflows -Recurse |
    Select-String -Pattern "node-version|node20"

You might find:

- uses: actions/setup-node@v4
  with:
    node-version: 20

This is an explicit application-level Node dependency.

It means commands such as:

- run: npm ci
- run: npm test

will use the configured Node environment.

Step 3 - Search for setup-node

Search for:

grep -Rni "setup-node" .github/workflows

A workflow might contain:

- uses: actions/setup-node@v4
  with:
    node-version: 20

This is straightforward to identify.

But setup-node is not the only place where Node.js can appear.

A workflow can use Node through an action without explicitly configuring a Node version.

Step 4 - Find Every uses: Entry

Search all workflows for:

uses:

For example:

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
  - uses: actions/setup-dotnet@v4
  - uses: docker/setup-buildx-action@v3

Create an inventory:

Action

Version

Purpose

Node Dependency

actions/checkout

v4

Repository checkout

JavaScript action

actions/setup-node

v4

Configure Node

Explicit

actions/setup-dotnet

v4

Configure .NET

JavaScript action

Buildx action

v3

Docker build setup

Check implementation

The exact runtime requirement should be verified against the action version currently used.

Do not infer the runtime only from the action name.

Step 5 - Understand JavaScript Actions

A JavaScript GitHub Action commonly contains an action.yml file.

A simplified example looks like:

name: Example Action

runs:
  using: node20
  main: dist/index.js

The important value is:

using: node20

This means the action expects the Node 20 runtime provided for GitHub Actions.

An older action could instead contain:

runs:
  using: node16
  main: dist/index.js

This is an action-level runtime dependency.

It is different from:

- uses: actions/setup-node@v4
  with:
    node-version: 20

The second example configures Node for commands that run later in the job.

Step 6 - Inspect Third-Party Actions

Third-party actions deserve special attention.

Consider:

- uses: company/deployment-action@v1

You should check the referenced action's implementation or documentation to determine which runtime it uses.

If it is a JavaScript action, inspect its action.yml or action.yaml.

For example:

runs:
  using: node20
  main: dist/index.js

If the action still uses an older runtime, check whether a newer maintained version exists.

Do not update it blindly.

A major version can change inputs, outputs, behavior, permissions, or other requirements.

Step 7 - Search the Whole Repository

After checking workflows, search the entire repository for Node 20 references.

On Linux or macOS:

grep -Rni --exclude-dir=.git "node:20\|node-version.*20\|node20" .

On PowerShell:

Get-ChildItem -Recurse -File |
    Select-String -Pattern "node:20|node-version.*20|node20"

You may discover references in:

package.json
Dockerfile
.nvmrc
.tool-versions
CI scripts
Build scripts
Documentation
Development containers

Not every match requires a change.

The purpose of the search is to understand where Node 20 is being used.

Step 8 - Check package.json

For Node-based projects, inspect:

package.json

You may find an engines declaration:

{
  "engines": {
    "node": ">=20"
  }
}

Or a project may use dependencies that require a particular Node version.

You can also check the package manager configuration.

For example:

.npmrc
package-lock.json
yarn.lock
pnpm-lock.yaml

These files can provide additional information about the build environment.

Step 9 - Check .nvmrc

Some projects specify their development Node version using:

.nvmrc

For example:

20

This usually controls the local development environment rather than the internal runtime of GitHub Actions.

A workflow may still use:

- uses: actions/setup-node@v4
  with:
    node-version-file: '.nvmrc'

In that case, the workflow explicitly inherits the Node version from the repository configuration.

This creates a dependency between the workflow and .nvmrc.

Step 10 - Check Dockerfiles

Node versions are frequently hidden inside container definitions.

For example:

FROM node:20

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

This is an application build dependency.

Changing the Node runtime used by GitHub Actions does not automatically mean this Docker image should change.

The correct question is:

Does the application support another Node version?

That needs to be answered independently.

Distinguish Action Runtime From Application Runtime

This is the most important concept in this entire process.

Consider:

steps:
  - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: 20

  - run: npm test

There are two different Node concepts here.

The first is the runtime used internally by actions/checkout.

The second is the Node runtime configured for:

npm test

Changing one does not necessarily change the other.

A useful mental model is:

GitHub Actions infrastructure
          |
          +-- Action runtime
          |
          +-- Runner
          |
          +-- Shell commands
                    |
                    +-- Application runtime

Treat each layer independently.

Example - A .NET Repository

Suppose your repository contains:

src/
tests/
.github/workflows/
Dockerfile

Your workflow is:

name: .NET CI

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.x'

      - run: dotnet restore
      - run: dotnet build --no-restore
      - run: dotnet test --no-build

There is no application-level Node dependency.

However, actions/checkout and actions/setup-dotnet can still use JavaScript internally.

Therefore, a search for:

node-version: 20

will not necessarily reveal every Node-related workflow dependency.

Example - A Node.js Repository

Now consider:

name: Node CI

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci
      - run: npm test

Here Node 20 is explicitly part of the application's CI environment.

If the project moves to another supported Node release, the workflow needs to be updated and tested.

This is separate from any Node runtime used internally by actions/checkout or actions/setup-node.

Search for Node Commands

A workflow can also invoke Node directly.

Search for:

node
npm
npx
yarn
pnpm

For example:

- run: node scripts/generate.js
- run: npm run build

These commands depend on the Node runtime installed in the job.

A workflow may therefore have an implicit Node dependency even without setup-node.

For example:

- run: npm ci

If the runner provides Node, the workflow can use it.

For production CI, however, explicitly defining the required runtime is generally easier to understand and reproduce.

Build a Dependency Inventory

Once you have searched the repository, create a simple inventory.

Location

Example

Dependency Type

Action

Workflow

setup-node

Application runtime

Review

Workflow

uses:

Action runtime

Check action

.nvmrc

20

Developer runtime

Review

package.json

engines.node

Application requirement

Review

Dockerfile

node:20

Container runtime

Review

Build script

npm test

Application runtime

Review

Third-party action

using: node20

Action runtime

Check release

This prevents unrelated Node references from being mixed together.

Common Mistakes

Searching Only for node-version

This misses Node runtime dependencies inside JavaScript actions.

Updating Every Node 20 Reference

A Node 20 Docker image and a GitHub Action runtime are different dependencies.

Ignoring Third-Party Actions

Internal and third-party actions may contain the most important compatibility issues.

Updating Major Versions Without Testing

Changing:

some-action@v1

to:

some-action@v2

can introduce breaking changes.

Checking Only the Main Workflow

Release, deployment, security, and scheduled workflows may use different actions.

Troubleshooting

The Workflow Uses Node 20 but You Cannot Find setup-node

Search for:

node
npm
npx
uses:

The Node dependency may come from a command or an action.

An Action Reports a Node Runtime Warning

Identify the action that produced the warning.

Then inspect its current version and implementation.

Updating an Action Breaks the Workflow

Check:

  • Inputs

  • Outputs

  • Permissions

  • Runner compatibility

  • Runtime requirements

  • Release notes

Node Application Tests Fail After a Runtime Update

Check the actual runtime:

node --version
npm --version

Then run:

npm ci
npm test

Look for dependency compatibility problems.

Docker Builds Still Use Node 20

Check whether the Node version is defined in:

Dockerfile
docker-compose.yml
container configuration

An Actions runtime migration does not automatically modify container images.

Best Practices

Maintain an Action Inventory

Keep track of the actions used across your repository.

Review Runtime Dependencies Regularly

Do not wait until a workflow starts failing.

Pin Versions Intentionally

Use explicit action versions and update them through normal code review.

Separate Runtime Layers

Document the difference between:

Application Node
Action Node
Docker Node
Local Development Node

Test CI Changes

After updating an action or application runtime, verify:

Build
Unit tests
Integration tests
Packaging
Deployment

Remove Obsolete Configuration

Once a migration is complete, remove unused Node versions and old workflow settings.

Advantages of a Proper Dependency Inventory

  1. Makes migrations easier - You know exactly where Node is being used.

  2. Reduces unexpected CI failures - Hidden dependencies become visible.

  3. Improves maintenance - Action and application dependencies can be updated independently.

  4. Makes troubleshooting faster - Runtime problems can be isolated by layer.

  5. Improves documentation - New developers can understand the CI environment more quickly.

Disadvantages and Limitations

  1. Repositories can contain many runtime references.

  2. Third-party actions may hide implementation details.

  3. A text search cannot always determine the actual runtime dependency.

  4. Different workflows can use different versions.

  5. Action behavior can change between releases.

Practical Node 20 Audit Checklist

Use this checklist before making a runtime migration:

[ ] Search every .github/workflows file
[ ] Find every uses: action
[ ] Search for setup-node
[ ] Search for node-version
[ ] Search for node20
[ ] Search for npm, npx, yarn, and pnpm
[ ] Check third-party JavaScript actions
[ ] Inspect action.yml when necessary
[ ] Check package.json
[ ] Check .nvmrc
[ ] Check Dockerfiles
[ ] Check development container configuration
[ ] Check build scripts
[ ] Separate application runtime from action runtime
[ ] Review action release notes
[ ] Test the complete workflow

Conclusion

Finding Node 20 dependencies in GitHub Actions is more involved than searching for node-version: 20.

Node.js can appear in several layers of a repository. It can be the runtime for application commands, a container runtime, a local development requirement, or the runtime used internally by a JavaScript GitHub Action.

The most important step is to classify each dependency before changing it.

Start by auditing every workflow, inventorying all uses: actions, searching for explicit Node configuration, checking third-party action implementations, and then reviewing application and container configuration separately.

Once the dependencies are mapped, runtime migrations become much more controlled. Instead of changing every Node 20 reference and hoping CI continues to work, you can update each dependency according to what it actually does and verify the result with targeted tests.