From d76f58af79d38664b292e4890b178ef3a0cbb7aa Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 24 Jun 2026 15:54:51 +0200 Subject: [PATCH 1/4] fix: correct invalid Node24 execution handler and add minimumAgentVersion The execution blocks used "Node24_1", which is not a valid Azure Pipelines handler name and was therefore silently ignored by every agent. The correct key is "Node24". This meant tasks never ran on the Node 24 runtime and, on agents that recognized none of the listed handlers, failed with the misleading "A supported task execution handler was not found ... not compatible with your current operating system" error (issue #31). - Rename Node24_1 -> Node24 in all 5 task.json files. - Keep Node20_1 (modern agents) and plain Node20 (on-prem ADO Server v3.224-v3.230, added deliberately in #29). - Add minimumAgentVersion 3.224.1 (release where the Node 20 handler first shipped, the floor of the lowest handler) so older agents get a clear minimum-version error instead of the cryptic handler message. This cannot block any agent that could otherwise run the task. - Normalize execution-block indentation and trailing newline. Refs #31, #29 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tasks/detect-tfm-bc-artifact/task.json | 12 +++++++----- tasks/detect-tfm-marketplace/task.json | 12 +++++++----- tasks/detect-tfm-nuget-devtools/task.json | 12 +++++++----- tasks/download/task.json | 13 +++++++------ tasks/install-analyzers/task.json | 12 +++++++----- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/tasks/detect-tfm-bc-artifact/task.json b/tasks/detect-tfm-bc-artifact/task.json index 108f340..cc84eb7 100644 --- a/tasks/detect-tfm-bc-artifact/task.json +++ b/tasks/detect-tfm-bc-artifact/task.json @@ -13,6 +13,7 @@ "Minor": 0, "Patch": 0 }, + "minimumAgentVersion": "3.224.1", "instanceNameFormat": "ALCops Detect TFM from BC Artifact", "inputs": [ { @@ -35,14 +36,15 @@ } ], "execution": { - "Node24_1": { + "Node24": { "target": "dist/index.js" }, "Node20_1": { "target": "dist/index.js" }, - "Node20": { - "target": "dist/index.js" - } - } + "Node20": { + "target": "dist/index.js" + } + } } + diff --git a/tasks/detect-tfm-marketplace/task.json b/tasks/detect-tfm-marketplace/task.json index 9c800ef..fcc9741 100644 --- a/tasks/detect-tfm-marketplace/task.json +++ b/tasks/detect-tfm-marketplace/task.json @@ -13,6 +13,7 @@ "Minor": 0, "Patch": 0 }, + "minimumAgentVersion": "3.224.1", "instanceNameFormat": "ALCops Detect TFM from VS Marketplace", "inputs": [ { @@ -51,14 +52,15 @@ } ], "execution": { - "Node24_1": { + "Node24": { "target": "dist/index.js" }, "Node20_1": { "target": "dist/index.js" }, - "Node20": { - "target": "dist/index.js" - } - } + "Node20": { + "target": "dist/index.js" + } + } } + diff --git a/tasks/detect-tfm-nuget-devtools/task.json b/tasks/detect-tfm-nuget-devtools/task.json index 0372dc2..c2d1e70 100644 --- a/tasks/detect-tfm-nuget-devtools/task.json +++ b/tasks/detect-tfm-nuget-devtools/task.json @@ -13,6 +13,7 @@ "Minor": 0, "Patch": 0 }, + "minimumAgentVersion": "3.224.1", "instanceNameFormat": "ALCops Detect TFM from NuGet DevTools", "inputs": [ { @@ -35,14 +36,15 @@ } ], "execution": { - "Node24_1": { + "Node24": { "target": "dist/index.js" }, "Node20_1": { "target": "dist/index.js" }, - "Node20": { - "target": "dist/index.js" - } - } + "Node20": { + "target": "dist/index.js" + } + } } + diff --git a/tasks/download/task.json b/tasks/download/task.json index 1df928e..369be5d 100644 --- a/tasks/download/task.json +++ b/tasks/download/task.json @@ -12,6 +12,7 @@ "Minor": 0, "Patch": 0 }, + "minimumAgentVersion": "3.224.1", "instanceNameFormat": "ALCops Download Analyzers $(version)", "inputs": [ { @@ -87,14 +88,14 @@ } ], "execution": { - "Node24_1": { + "Node24": { "target": "dist/index.js" }, "Node20_1": { "target": "dist/index.js" }, - "Node20": { - "target": "dist/index.js" - } - } -} \ No newline at end of file + "Node20": { + "target": "dist/index.js" + } + } +} diff --git a/tasks/install-analyzers/task.json b/tasks/install-analyzers/task.json index 12017ab..c4553c9 100644 --- a/tasks/install-analyzers/task.json +++ b/tasks/install-analyzers/task.json @@ -13,6 +13,7 @@ "Minor": 0, "Patch": 0 }, + "minimumAgentVersion": "3.224.1", "instanceNameFormat": "ALCops Install Analyzers $(version)", "inputs": [ { @@ -94,14 +95,15 @@ } ], "execution": { - "Node24_1": { + "Node24": { "target": "dist/index.js" }, "Node20_1": { "target": "dist/index.js" }, - "Node20": { - "target": "dist/index.js" - } - } + "Node20": { + "target": "dist/index.js" + } + } } + From 27774aa477893c521da5ede412d2474c4eacab2e Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 24 Jun 2026 15:55:03 +0200 Subject: [PATCH 2/4] fix: align esbuild target with the Node 20 runtime All three execution handlers point to the same dist/index.js, so the single shared bundle can be selected to run on the Node 20 runtime (Node20_1 / Node20 handlers). Targeting node24 risked emitting syntax newer than Node 20 can run. Lower the esbuild target to node20 so the bundle is valid on the lowest runtime it can land on. esbuild target is a compatibility floor, not an optimization, so this has no effect on Node 24 execution. Refs #31 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- esbuild.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index ebc5ced..55c7e76 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -15,7 +15,7 @@ const sharedOptions = { bundle: true, format: 'cjs', platform: 'node', - target: 'node24', + target: 'node20', sourcemap: !isProduction, minify: isProduction, logLevel: 'info', From fba689e774cbf4fa69dfb3a07dfd4a27f51d2c37 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 24 Jun 2026 15:55:05 +0200 Subject: [PATCH 3/4] test: update scaffold handler assertions and guard against Node24_1 Assert the valid Node24 handler (not Node24_1), require Node20_1 and Node20, add a guard that the invalid Node24_1 key is absent, and assert the new minimumAgentVersion value. Refs #31 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/scaffold.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/scaffold.test.ts b/tests/scaffold.test.ts index 96f19e1..a6fe835 100644 --- a/tests/scaffold.test.ts +++ b/tests/scaffold.test.ts @@ -53,9 +53,15 @@ describe('scaffold: task.json files', () => { const taskJson = JSON.parse(fs.readFileSync(taskJsonPath, 'utf-8')); expect(taskJson.id).toBeTruthy(); expect(taskJson.name).toBeTruthy(); - expect(taskJson.execution).toHaveProperty('Node24_1'); + expect(taskJson.execution).toHaveProperty('Node24'); expect(taskJson.execution).toHaveProperty('Node20_1'); expect(taskJson.execution).toHaveProperty('Node20'); + // Guard against the invalid handler name reappearing (Node24_1 is not a + // recognized Azure Pipelines handler; the correct key is Node24). + expect(taskJson.execution).not.toHaveProperty('Node24_1'); + // minimumAgentVersion gives old agents a clear error instead of the cryptic + // "supported task execution handler was not found" message. + expect(taskJson.minimumAgentVersion).toBe('3.224.1'); }); it(`should have src/index.ts for ${taskDir}`, () => { From aa6450ebd0b30e0d2acb8e6a8418fc4eb9a72ab9 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 24 Jun 2026 15:55:15 +0200 Subject: [PATCH 4/4] docs: add handler troubleshooting and fix Node24_1 references - README: add a Troubleshooting section explaining the misleading "supported task execution handler was not found" error, the v3.224.1 agent baseline, and the upgrade / NodeTaskRunnerInstaller@0 workarounds (incl. on-prem). - Replace incorrect Node24_1 references with Node24 and note the new minimumAgentVersion in ARCHITECTURE.md, copilot-instructions.md, and CONTRIBUTING.md. Refs #31 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/ARCHITECTURE.md | 2 +- .github/copilot-instructions.md | 6 +++--- CONTRIBUTING.md | 4 ++-- README.md | 28 ++++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/ARCHITECTURE.md b/.github/ARCHITECTURE.md index 60595ea..24a6b42 100644 --- a/.github/ARCHITECTURE.md +++ b/.github/ARCHITECTURE.md @@ -254,7 +254,7 @@ PR / Feature Branch Push to main Tag v* On the Azure DevOps agent: -1. Agent picks the best available Node handler: `Node24_1` → `Node20_1` → `Node20` (fallback) +1. Agent picks the best available Node handler: `Node24` → `Node20_1` → `Node20` (fallback) 2. Agent runs `tasks//dist/index.js` 3. `index.ts` calls `task-runner.ts` → `run()` 4. Task-runner reads inputs via `tl.getInput()`, executes logic, sets outputs via `tl.setVariable()` diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e852029..0d57eaa 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -69,7 +69,7 @@ Every task follows the same pattern: ### task.json Each task has a `task.json` defining its Azure DevOps contract: -- Must include both `Node24_1` (primary), `Node20_1`, and `Node20` (fallback) in `execution` +- Must include `Node24` (primary), `Node20_1`, and `Node20` (fallback) in `execution`, plus a `minimumAgentVersion` of `3.224.1` - Task `id` is a stable GUID (never changes) - Task `Major` version only bumps for breaking YAML contract changes - `Minor` and `Patch` are stamped by CI via inline `jq` in the workflow YAML @@ -123,7 +123,7 @@ vi.mock('@alcops/core', async (importOriginal) => { ## Adding a New Task -1. Create `tasks//task.json` — unique GUID, Node24_1 + Node20_1 + Node20 handlers +1. Create `tasks//task.json` — unique GUID, Node24 + Node20_1 + Node20 handlers, `minimumAgentVersion` `3.224.1` 2. Create `tasks//src/index.ts` (calls `run()`, guards with `.catch`) and `src/task-runner.ts` (reads inputs → calls `@alcops/core` → sets outputs) 3. Add the task name to the `tasks` array in `esbuild.config.mjs` 4. Add entries in `vss-extension.json` `files` and `contributions` arrays (and `vss-extension.dev.json`) @@ -145,7 +145,7 @@ TypeScript and vitest both use the `@shared/*` alias for imports from `shared/`: ## Common Pitfalls -- **Missing Node handler**: every `task.json` needs `Node24_1`, `Node20_1`, and `Node20` execution entries +- **Missing/invalid Node handler**: every `task.json` needs `Node24`, `Node20_1`, and `Node20` execution entries (note: `Node24_1` is NOT a valid handler name — the correct key is `Node24`). Also set `minimumAgentVersion` `3.224.1` so old agents get a clear error. - **Shared modules aren't runtime-shared**: `shared/` helpers and `@alcops/core` are bundled into each task by esbuild. No `node_modules` sharing at runtime. - **Logic lives in `@alcops/core`, not here**: TFM detection, NuGet download, ZIP/PE parsing are all in the external package. Wrappers must stay thin — don't reimplement or duplicate core's validation (e.g. valid TFM lists) in the wrapper. - **Output variables need `isOutput: true`**: the 4th argument to `tl.setVariable()` must be `true` for downstream tasks to read the value diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d265097..804f6ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,7 @@ npx vitest --watch # Watch mode ### Adding a New Task -1. Create `tasks//task.json` with a unique GUID, Node24_1 + Node20_1 + Node20 handlers +1. Create `tasks//task.json` with a unique GUID, Node24 + Node20_1 + Node20 handlers, and `minimumAgentVersion` `3.224.1` 2. Create `tasks//src/index.ts` (entry point) and `src/task-runner.ts` (orchestrator) 3. Add task to `esbuild.config.mjs` `tasks` array 4. Add task to `vss-extension.json` `files` and `contributions` arrays @@ -219,5 +219,5 @@ Before submitting a PR: - [ ] `.vsix` packages (`npm run package`) - [ ] New code has tests (TDD — write tests first) - [ ] Shared module changes tested across all affected tasks -- [ ] `task.json` has `Node24_1`, `Node20_1`, and `Node20` execution handlers +- [ ] `task.json` has `Node24`, `Node20_1`, and `Node20` execution handlers (not `Node24_1`) and `minimumAgentVersion` `3.224.1` - [ ] No secrets or credentials in source code diff --git a/README.md b/README.md index 89ed91a..0942ae7 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,34 @@ Download ALCops code analyzers with automatic TFM detection. | `outputDir` | Full path to extracted analyzer DLLs directory | | `files` | Semicolon-separated list of analyzer DLL paths | +## Troubleshooting + +### "A supported task execution handler was not found ... not compatible with your current operating system" + +Despite mentioning the operating system, this Azure DevOps error is **not** about your OS. +It means your build agent is **too old** to recognize any of the task's Node execution +handlers. The tasks ship `Node24`, `Node20_1`, and `Node20` handlers and require an agent +of at least **v3.224.1** (the release that introduced the Node 20 handler). + +Fix it with any of the following: + +- **Upgrade the agent.** For self-hosted agents, update to the latest 3.x or 4.x agent. + Microsoft-hosted agents are always current. Self-hosted agents auto-update within a + major version but **not** across majors (v2→v3, v3→v4 require a manual upgrade). +- **Install a newer Node runner on the agent** by adding the + [`NodeTaskRunnerInstaller@0`](https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/node-task-runner-installer-v0) + task before the ALCops task: + + ```yaml + - task: NodeTaskRunnerInstaller@0 + inputs: + runnerVersion: 20 + ``` + +- **On-premises Azure DevOps Server:** ensure the server (and its bundled agent) is recent + enough. Servers older than the agent v3.224.1 baseline cannot run these tasks; upgrade + the server or use the `NodeTaskRunnerInstaller@0` workaround above. + ## Development ### Prerequisites