
Geschreven door Funs Janssen
Software Consultant
Ik ben Funs Janssen. Ik ontwikkel software en schrijf over de beslissingen die daarbij komen kijken: architectuur, ontwikkelmethoden, AI-tools en de zakelijke impact van technische keuzes. Deze blog is een verzameling praktische aantekeningen van echte projecten: wat schaalbaar is, wat misgaat en wat in blogvriendelijke voorbeelden vaak over het hoofd wordt gezien.
If you run Azure Pipelines, you have probably seen this line in a log by now: Task 'X' version 2 is dependent on a Node version (16) that is end-of-life. For a long time it was just noise. On November 24, 2026 it stops being noise, because Microsoft removes the Node 6, 10 and 16 runners from the pipeline agent. A task that can only run on one of those runners will stop working.
The usual advice is "contact the extension owner" or "add a Node24 handler". That is fine when you have one task. It is useless when you own an Azure DevOps organization with hundreds of pipelines, a handful of home-grown tasks written years ago, and a few Marketplace extensions nobody remembers installing.
This post gives you a practical route through it: query your organization for every task and its runners, sort the results into three buckets, fix each bucket the right way, and test it all before the deadline. I build Azure DevOps extensions for a living, so I'll also point out the traps that turn a ten-minute fix into a lost afternoon.
What changes on November 24, 2026
Node 6, 10 and 16 runners leave the agent
Every Node-based pipeline task declares which Node runtime it wants in the execution section of its task.json. The agent ships several runtimes side by side and picks the highest one the task supports. According to the Microsoft Learn page on Node.js runners in the Azure Pipelines agent, the end-of-life runners for Node 6, Node 10 and Node 16 are removed on November 24, 2026. Pipelines already warn when a task depends on one of them.
After removal, a task that only declares those runners has nothing left to run on.
Node 20 is not a safe landing spot
It is tempting to add a Node 20 handler and move on. Don't. Node 20 went out of support in April 2026 and is scheduled for removal from the agent in April 2027. Node 24 is supported until 2028. If you are touching a task anyway, target Node 24 and keep Node 20 only as a fallback for older agents. Otherwise you are signing up to do this exact exercise again in six months.
Who is affected
- Microsoft-hosted agents: yes, and you don't control when the change lands.
- Self-hosted agents: yes, as soon as they run an agent version without the old runners.
- Azure DevOps Server: the organization-level test setting described below is not available there, so you need a different test approach. More on that later.
It applies to tasks running directly on the agent host and to tasks running inside container jobs.
Step 1: Inventory every task in your organization
Why pipeline logs are the wrong starting point
The deprecation warning only appears when a pipeline actually runs the task. That quarterly release pipeline, the yearly certificate rotation, the disaster-recovery job nobody triggers: none of them show up in this week's logs. Reading logs tells you what broke recently, not what will break.
The better source of truth is the list of installed task definitions in your organization. It includes every built-in task, every task from a Marketplace extension, and every private task you uploaded, each with its declared runners.
Query the task definitions and list their handlers
The Azure DevOps REST API exposes installed tasks at https://dev.azure.com/{organization}/_apis/distributedtask/tasks. Each task definition contains its execution handlers, plus prejobexecution and postjobexecution for tasks with pre-job and post-job steps. The following PowerShell script flags every task that has Node handlers but no supported one:
1$org = "your-organization"2$pat = $env:AZDO_PAT # a PAT with Agent Pools (read) scope3$auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))4$headers = @{ Authorization = "Basic $auth" }56$tasks = (Invoke-RestMethod -Headers $headers `7 -Uri "https://dev.azure.com/$org/_apis/distributedtask/tasks").value89$supported = @("Node20_1", "Node24")1011$tasks | ForEach-Object {12 $handlers = @()13 foreach ($section in @($_.execution, $_.prejobexecution, $_.postjobexecution)) {14 if ($section) { $handlers += $section.PSObject.Properties.Name }15 }16 $node = $handlers | Where-Object { $_ -like "Node*" }17 if ($node -and -not ($node | Where-Object { $supported -contains $_ })) {18 [pscustomobject]@{19 Task = $_.name20 Version = "$($_.version.major).$($_.version.minor).$($_.version.patch)"21 Publisher = $_.author22 Handlers = ($node -join ", ")23 }24 }25} | Sort-Object Task, Version -Unique | Format-Table -AutoSize
Two notes. The plain Node handler means Node 6. And if your organization asks for an api-version on that call, add the one named in the error message. The output is your worklist: each row is a task version that will fail after the deadline.
To see which pipelines use a flagged task, search your YAML repositories for the task name (for example PostBuildCleanup@4) with Code Search, and check classic pipelines in the UI.
Sort the results into three buckets
- Tasks you own: private tasks and extensions your team publishes. You fix these yourself.
- Maintained Marketplace tasks: the publisher is active and a newer major version already declares Node 24 or Node 20_1.
- Abandoned tasks: no newer version, no response on issues, or no source code at all.
Each bucket has a different fix, so it pays to sort first.
Step 2: Test with "Restrict out of support Node.js versions"
What the setting does
Microsoft added an organization setting that simulates the deadline today. Go to Organization settings > Pipelines > Settings > Task restrictions and enable Restrict out of support Node.js versions in pipeline tasks. With it on, the agent refuses the end-of-life runner a task asks for and tries Node 24, then Node 20 instead. It logs a warning naming the affected task and fails the task only if neither works.
This is valuable because many simple tasks run fine on a newer Node, even without a declared handler. The setting tells you which ones actually break.
Use it in a controlled window
Turn it on at a quiet moment, announce it to your teams, and watch failed runs closely for a day or two. Every failure is a confirmed item for your worklist. If something critical breaks, switch the setting off again: it does not remove any runners, so reverting is instant. Keep it on as long as you can tolerate, because that is exactly how things will behave after November 24.
Step 3: Fix the tasks you own
The official Node 24 migration guide in the azure-pipelines-tasks repository is the reference. Here is the short version, with the parts that bite in practice.
Update the dependencies
In package.json:
typescriptto^5.7.2(devDependencies), then fix the type errors it surfaces@types/nodeto^24.10.0, or remove it if the task doesn't use built-in Node modules directlyazure-pipelines-task-libto^5.2.4,azure-pipelines-tool-libto^2.0.10,azure-devops-node-apito^15.1.3where used
Make sure shared npm packages inside the task don't pull in a different version of task-lib. Two copies of task-lib in one task cause strange, hard-to-trace errors because the library relies on shared global state.
Add the Node24 handler and drop the old ones
1"execution": {2 "Node20_1": { "target": "index.js", "argumentFormat": "" },3 "Node24": { "target": "index.js", "argumentFormat": "" }4}
Remove Node, Node10 and Node16 entries. Keeping them buys you nothing after the deadline and hides problems before it.
The Node20 vs Node20_1 trap
This one catches people who already "upgraded" years ago. Current agents ship a runtime called node20_1, not node20. A task that declares "Node20" does not match it, so the agent silently falls back to the next handler it can find, often Node 16, and you get the end-of-life warning on a task you thought was modern. Richard Fennell documented exactly this with the Post Build Cleanup task. The inventory script above treats a bare Node20 as unsupported for this reason.
Set minimumAgentVersion and bump the task version
- Set
minimumAgentVersionto4.265.1, the first agent that supports theNode24handler. This triggers an automatic upgrade on older self-hosted agents instead of letting the task fail on them. - Bump the task version in
task.json. Agents cache tasks by version. Republish a changed task under the same version number and many agents will happily keep running the old code. - Bump the extension version in
vss-extension.jsonas well.
Repackage and publish
Package and publish with tfx extension create and tfx extension publish, as described in Microsoft's guide to adding a custom pipelines task. Then run a real pipeline that uses the task on both a Microsoft-hosted agent and one of your self-hosted agents, with the restriction setting enabled.
Step 4: Tasks you don't own
Maintained Marketplace tasks
Update the Marketplace extension, then bump the task's major version in your YAML, for example SomeTask@2 to SomeTask@3. Read the task's changelog first: a new major version can change inputs. Do this in a branch and run the pipeline before merging.
Abandoned tasks
You have three options, roughly in order of preference:
- Replace it with a script. Many small tasks are thin wrappers around a CLI. A
PowerShell@2orBash@3step calling the CLI directly has no Node dependency at all and is easier to read. - Fork it. If the source is open, fork it, apply Step 3, and publish it privately to your organization. You now own the maintenance.
- Rebuild it. If the task does something genuinely useful and has no source, rebuilding a clean, tested version is often less work than reverse-engineering a packaged one.
Whatever you choose, record who owns the replacement. Abandoned tasks become abandoned because nobody owned them in the first place.
Self-hosted agents and Azure DevOps Server
Old agents and old Windows hosts
Node 24 has stricter operating system requirements than Node 16. If you run self-hosted agents on older Windows Server builds, test a Node 24 task on those hosts before you rely on it, and plan an OS upgrade for any that can't run it. Also keep agent versions current. An agent too old to know the Node24 handler will fall back to lower handlers or fail when minimumAgentVersion can't be met.
Azure DevOps Server
The restriction setting is not available on Azure DevOps Server. There, the risk arrives when you upgrade the server or agents to a version without the old runners. Run the same inventory against your collection URL, fix tasks the same way, and test on a staging agent running the new agent version before you roll it out.
A four-week plan to finish before the deadline
- Week 1: inventory. Run the script, sort into three buckets, and map which pipelines use each flagged task.
- Week 2: test. Enable the restriction setting, collect failures, and update maintained Marketplace tasks.
- Week 3: fix. Upgrade your own tasks, replace or fork abandoned ones, and publish new versions.
- Week 4: confirm. Leave the setting on, rerun rarely used pipelines by hand, and upgrade old self-hosted agents.
If you track this as work items, a reusable checklist keeps it honest. I wrote about building one in this guide to a release checklist in Azure DevOps, and the free Checklist Extension for Azure DevOps can block a work item from moving to Done until every step is ticked. For teams still getting the basics in place, the lean Azure DevOps setup for small teams is a useful companion.
Conclusion
The Node 16 removal is not hard, but it is easy to underestimate. The key moves:
- Inventory from task definitions, not from logs, so rarely used pipelines don't surprise you.
- Test with the restriction setting now, while switching it off is still an option.
- Target Node 24, keep
Node20_1as a fallback, and never a bareNode20. - Bump task versions and
minimumAgentVersion, or your fix won't reach the agents. - Give abandoned tasks an owner, whether that is a script, a fork or a rebuild.
The tasks that hurt most in this migration are always the in-house ones: written by someone who left, never tested outside one pipeline, and now blocking a release. If that sounds familiar and you'd rather hand it off, I build and maintain Azure DevOps extensions and pipeline tasks. Have a look at my custom Azure DevOps extension development service and tell me what you're running.
Frequently asked questions
Reacties
Nog geen reacties. Wees de eerste om te reageren.
Plaats een reactie

Geschreven door Funs Janssen
Software Consultant
Ik ben Funs Janssen. Ik ontwikkel software en schrijf over de beslissingen die daarbij komen kijken: architectuur, ontwikkelmethoden, AI-tools en de zakelijke impact van technische keuzes. Deze blog is een verzameling praktische aantekeningen van echte projecten: wat schaalbaar is, wat misgaat en wat in blogvriendelijke voorbeelden vaak over het hoofd wordt gezien.
Inhoud

Use a release checklist Azure DevOps teams trust to standardize readiness, prevent incidents, and ship confidently every time.

Azure DevOps setup for small teams in 7 days: minimal process, clear boards, repos, pipelines, and branch policies to ship fast.
