GitHub Actions workflows depend on more than the commands written in a YAML file. Actions themselves can depend on specific Node.js runtimes, and changes to those runtimes can affect CI pipelines even when a repository's application code has not changed.
This becomes especially important when an action moves away from an older Node.js runtime.
If a workflow still depends on an action running on Node 20, developers may eventually see warnings, compatibility problems, or failures as GitHub Actions moves its runtime support forward.
The important question is not simply whether your application uses Node.js 20.
The real question is:
Which Node.js runtime do the actions inside your workflow require?
This distinction matters because a .NET, Java, Python, or C++ repository can still use JavaScript-based GitHub Actions internally.
GitHub Actions and Node.js Runtimes
GitHub Actions supports reusable actions written in different technologies. JavaScript actions run using a Node.js runtime supplied by the GitHub Actions runner.
A workflow might therefore contain:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: some-org/some-action@v2
Your application may be written entirely in C#, but actions/checkout or another third-party action can still depend on Node.js internally.
This creates two separate runtime questions:
Application runtime
+
GitHub Actions action runtime
They should not be treated as the same thing.
Why a Node Runtime Change Matters
GitHub periodically updates the Node.js versions used by its JavaScript-based actions.
This is necessary because older Node.js versions eventually reach the end of their supported lifecycle.
For workflow maintainers, the important consequence is that an action can require an updated runtime even when the workflow YAML looks unchanged.
For example:
- uses: example/action@v1
may continue to look valid while the implementation behind v1 depends on an older runtime.
A runtime migration can therefore affect the workflow indirectly.
Node 20 in Your Application vs Node 20 in Actions
This is one of the most common sources of confusion.
Consider a .NET workflow:
name: Build
on:
push:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.x'
- name: Build
run: dotnet build
There is no application-level Node.js requirement here.
However, some actions used by the workflow may internally use Node.js.
That means changing the Node runtime used by GitHub's action infrastructure does not necessarily mean you need to add:
node-version: '20'
to the workflow.
Do not add application runtime configuration simply because an action uses Node internally.
How to Find Node-Based Actions
The first step is to inspect your workflow files.
Look under:
.github/workflows/
For example:
.github/
└── workflows/
├── build.yml
├── test.yml
├── release.yml
└── deployment.yml
Then identify every uses: entry:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: docker/setup-buildx-action@v3
- uses: org/custom-action@v1
The important question for each action is:
Which runtime does this version of the action require?
Do not determine this only from the action name.
An action can change its internal runtime between major or minor releases.
Why Third-Party Actions Need Special Attention
Official actions are usually easier to track because their repositories and release notes are widely documented.
Third-party actions can be more difficult.
For example:
- uses: company/internal-deployment-action@v1
If that action has not been maintained recently, it may continue using an older Node runtime.
The workflow may then encounter warnings or compatibility problems even though the repository itself is actively maintained.
This is why action maintenance should be part of CI maintenance.
Finding Old Action Versions
Search your workflow files for uses:.
On Linux or macOS:
grep -R "uses:" .github/workflows
On PowerShell:
Get-ChildItem .github/workflows -Recurse |
Select-String "uses:"
This gives you a starting list.
For example:
.github/workflows/build.yml
.github/workflows/test.yml
.github/workflows/release.yml
You can then inspect each referenced action.
Check the Action's Runtime
For JavaScript actions, the repository commonly contains an action.yml or action.yaml file.
A JavaScript action can contain information similar to:
runs:
using: node20
main: dist/index.js
The important field is:
using: node20
This indicates which Node.js runtime the action expects.
An older action might contain:
runs:
using: node16
main: dist/index.js
That is an action-runtime dependency, not necessarily an application dependency.
What Should You Use Instead?
The correct replacement depends on the action.
If an action has a newer maintained release that supports the required Node runtime, update the action.
For example:
- uses: example/action@v1
might become:
- uses: example/action@v2
The exact version should come from the action's documented release and compatibility information.
Do not blindly replace every action version with the newest major version.
Major releases can contain behavior changes.
Example - Updating a Workflow
Suppose an existing workflow contains:
steps:
- uses: actions/checkout@v3
- name: Run tests
run: dotnet test
If the repository is still using an old version of an action, the correct maintenance process is:
Identify action
↓
Check current release
↓
Check runtime requirement
↓
Review breaking changes
↓
Update action
↓
Run CI
↓
Verify results
Do not treat a runtime migration as a simple search-and-replace operation.
Do You Need setup-node?
Another common misunderstanding is that every workflow affected by a Node runtime change needs actions/setup-node.
For example:
- uses: actions/setup-node@v4
with:
node-version: 20
This is appropriate when your application or build process needs Node.js 20.
For example:
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
But if your repository is a .NET application and Node.js is only used internally by GitHub Actions, adding setup-node does not solve an action-runtime migration.
These are different layers.
Example - .NET Application With a Node-Based Action
Consider:
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 test
There is no reason to add setup-node simply because GitHub's internal action runtime changes.
The .NET SDK is the application build runtime.
The JavaScript runtime used by the action is an implementation detail of the action.
What About Node Projects?
Node applications are different.
Consider:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
Here Node 20 is explicitly part of the application's CI environment.
If your application requires Node 22, for example, you can configure:
- uses: actions/setup-node@v4
with:
node-version: 22
This controls the Node version available to your run: commands.
Again, this is separate from the Node runtime used internally by JavaScript actions.
How to Find Node 20 References
Search both workflow configuration and project configuration.
Search Workflows
grep -R "node20" .github/workflows
Also search for:
grep -R "node-version" .github/workflows
Search the Repository
Depending on your project:
grep -R "\"node\": \"20" .
or inspect:
package.json
.nvmrc
.tool-versions
For a mixed repository, search for references to Node 20 across CI scripts, build scripts, containers, and documentation.
The goal is to determine whether Node 20 is:
Application dependency
or
Action implementation dependency
Check Container-Based Workflows Too
Some workflows explicitly use Node images.
For example:
container:
image: node:20
This is different from a JavaScript action using Node 20 internally.
The container is part of your workflow environment.
If the application requires a newer Node version, the container image may need to change:
container:
image: node:22
Test the application after making such a change.
Docker Builds Need Separate Review
Node versions can also appear inside Dockerfiles.
For example:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
If your application still depends on Node 20, an Actions runtime migration does not automatically require changing this Dockerfile.
Again, separate the concerns:
GitHub Actions runtime
≠
Docker runtime
≠
Application runtime
Each needs its own compatibility check.
Common Mistakes
Changing Every Node Version to the Same Value
Do not replace every 20 with another Node version without checking what the value represents.
Node 20 can appear in:
Application configuration
Docker images
GitHub Actions
Local development files
Documentation
Test environments
They may have different requirements.
Adding setup-node Without Understanding the Problem
setup-node changes the Node version available to commands executed by the job.
It does not necessarily change the runtime embedded in another JavaScript action.
Ignoring Third-Party Actions
A third-party action may remain on an old runtime long after the rest of the workflow has been updated.
Updating Actions Without Reading Release Notes
A major action upgrade can introduce behavior changes.
Test the workflow after upgrading.
Checking Only One Workflow
Repositories commonly have multiple workflow files:
build.yml
test.yml
release.yml
deploy.yml
security.yml
An old action can remain in only one of them.
Troubleshooting Runtime Migration Problems
Workflow Shows a Node Runtime Warning
Identify which action produced the warning.
Do not assume the warning comes from your application.
Inspect the workflow step associated with the message and check the action version.
Action Fails After an Upgrade
Compare the old and new action versions.
Look for:
Changed inputs
Removed inputs
Changed outputs
Permission requirements
Runner requirements
Runtime requirements
Node Application Fails After Updating Node
Run the application's tests separately.
Check:
node --version
npm --version
npm ci
npm test
Also inspect package compatibility.
.NET Workflow Fails After an Action Update
Check whether the updated action changed its inputs or outputs.
Then run:
dotnet restore
dotnet build
dotnet test
The action runtime and .NET runtime should be diagnosed separately.
Best Practices for GitHub Actions Runtime Changes
Keep Actions Updated
Do not leave workflow actions untouched for years.
Regular updates reduce the chance of being forced into an emergency migration.
Pin Action Versions Carefully
Use intentional action versions instead of relying on an unreviewed moving target.
For higher-security environments, commit SHA pinning can provide stronger supply-chain control, although it increases maintenance overhead.
Test Changes in Pull Requests
Action updates should go through the same review process as application changes.
Maintain a Workflow Inventory
Keep track of:
Action
Version
Purpose
Runtime
Owner
Update frequency
For example:
Action | Purpose | Runtime Concern |
|---|---|---|
Checkout action | Fetch repository | JavaScript runtime |
Setup Node | Configure application Node | Explicit Node version |
Setup .NET | Configure SDK | Action runtime separate from .NET |
Deployment action | Deploy application | Check action implementation |
Custom action | Internal automation | Verify maintained runtime |
Separate Application and Action Runtime Changes
When troubleshooting, identify which layer changed.
This makes failures much easier to diagnose.
Advantages of Keeping Workflows Current
Fewer runtime compatibility surprises
Better support for current runner environments
Improved security maintenance
Easier troubleshooting
Less risk of emergency migration work
Disadvantages and Risks of Updating Too Quickly
Major action upgrades can introduce breaking changes.
Old workflows may depend on undocumented behavior.
Third-party actions may not have compatible releases.
Testing is required before production deployment.
Updating multiple actions simultaneously makes troubleshooting harder.
A Practical Migration Checklist
Before changing a workflow, check:
[ ] List every action used by the workflow
[ ] Identify actions that execute JavaScript
[ ] Check their current runtime requirements
[ ] Identify Node versions used by application commands
[ ] Check Docker and container images
[ ] Check package configuration
[ ] Review action release notes
[ ] Update one logical dependency at a time
[ ] Run the workflow in a pull request
[ ] Verify build and tests
[ ] Verify deployment jobs separately
[ ] Remove obsolete configuration
Conclusion
When GitHub Actions moves away from an older Node.js runtime, the first step is not to change every Node version in your repository.
Instead, determine where Node.js is being used.
A repository can use Node.js in several independent places: GitHub Actions themselves, application builds, Docker containers, local development, and package tooling.
The Node runtime used internally by a JavaScript GitHub Action is different from the Node runtime configured with actions/setup-node.
That distinction makes migrations much easier to understand.
The safest approach is to inventory your workflows, identify the actions that depend on older runtimes, update those actions to supported releases, test the complete workflow, and separately maintain any Node.js version required by the application itself.
Keeping GitHub Actions dependencies current is ongoing maintenance rather than a one-time migration task. A small amount of regular review can prevent an old action runtime from becoming a CI failure later.

Join the conversation! Your thoughts help the community grow.