From 5da4b7dd5cac468f02d5e340ef712a0ac530a867 Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Thu, 10 Sep 2026 14:21:55 -0700 Subject: [PATCH 1/9] Adopt standalone Helix monitoring for SDK performance CI Pair internal asynchronous submissions with a credentialed stage monitor and pin its tool alongside the Helix SDK. Preserve public, PR, and local synchronous behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .config/dotnet-tools.json | 12 ++++++++++++ azure-pipelines.yml | 18 ++++++++++++++++++ docs/sdk-scenarios.md | 26 ++++++++++++++++++++++++++ eng/Version.Details.xml | 4 ++++ 4 files changed, 60 insertions(+) create mode 100644 .config/dotnet-tools.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000000..5f56247a879 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "microsoft.dotnet.helix.jobmonitor": { + "version": "11.0.0-beta.26458.103", + "commands": [ + "dotnet-helix-job-monitor" + ] + } + } +} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4cd188a6a74..5fa85dc4c40 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,6 +12,16 @@ parameters: type: boolean default: false +variables: + # This entrypoint has one implicit stage. Keep asynchronous sends paired with its monitor, + # and leave public/PR runs and manual runs without selected jobs on the synchronous default. + - name: EnableHelixJobMonitor + value: ${{ and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest'), or(ne(variables['Build.Reason'], 'Manual'), parameters.runPublicJobs, parameters.runPrivateJobs, parameters.runScheduledPrivateJobs)) }} + - ${{ if eq(variables.EnableHelixJobMonitor, 'true') }}: + - template: /eng/common/templates/variables/pool-providers.yml + # The standalone monitor does not inherit the submitter jobs' variable groups. + - group: DotNet-HelixApi-Access + resources: containers: - container: ubuntu_x64_build_container @@ -67,3 +77,11 @@ jobs: jobParameters: ${{ if parameters.onlySanityCheck }}: onlySanityCheck: true + + - ${{ if eq(variables.EnableHelixJobMonitor, 'true') }}: + - template: /eng/common/core-templates/job/helix-job-monitor.yml + parameters: + # Allow the existing 320-minute submitter budget plus six-hour Helix work items. + timeoutInMinutes: 720 + helixAccessToken: $(HelixApiAccessToken) + allowNoHelixJobs: false diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index 12e2c952289..34d1630292c 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -4,6 +4,32 @@ An introduction of how to run scenario tests can be found in [Scenarios Tests Gu - [SDK Build Throughput Scenario](#sdk-build-throughput-scenario) +## SDK performance pipeline + +The repository's [azure-pipelines.yml](../azure-pipelines.yml) entrypoint uses +[sdk-perf-jobs.yml](../eng/pipelines/sdk-perf-jobs.yml) for SDK-based scenario and +benchmark jobs. It backs the internal `dotnet-performance` pipeline (definition 306) +and the public `performance-ci` pipeline (definition 38). + +Internal non-PR runs with selected jobs submit asynchronously and use the standalone +Helix Job Monitor in the same implicit stage. The monitor runs alongside submitters, +waits for their Helix work items, and reports failures to Azure DevOps. Public and PR +runs remain synchronous. The existing manual job-selection parameters and schedules +are unchanged; a manual run with no jobs selected does not start a monitor. A monitored +run with no Helix jobs fails rather than silently succeeding. + +The entrypoint imports `DotNet-HelixApi-Access` only when the monitor is enabled, so +the standalone job receives its own credential rather than depending on a submitter's +job-scoped variables. Its tool is restored from `.config/dotnet-tools.json` in its own +checkout; `eng/Version.Details.xml` tracks that pin alongside the Helix SDK. + +Submitter build logs still use the existing `Logs_*` pipeline artifacts. Benchmark +results and diagnostics are still uploaded from the Helix work items to PerfLab and +Helix, respectively. The monitor reports test results and links to Helix consoles; it +does not download arbitrary Helix uploads into pipeline artifacts. Asynchronous sends +skip the submitter's `artifacts/helix-results` downloads, which were not published by +this pipeline. Local `--send-to-helix` runs still wait and download performance reports. + ## SDK Build Throughput Scenario **SDK Build Throughput** is a scenario test that measures the throughput of SDK build process. To be more specific, our test *implicitly calls* diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 37c4f335c2a..40f2fd8843c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -35,6 +35,10 @@ https://github.com/dotnet/dotnet bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + + https://github.com/dotnet/dotnet + bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + https://github.com/dotnet/xharness 1665037768fd7eec44e046d94fd07b8c6407c966 From ad8f881dc88998c4a31374ff6e78c44632fd072b Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Thu, 10 Sep 2026 14:28:46 -0700 Subject: [PATCH 2/9] Keep SDK monitor variables in one template guard Evaluate the internal-run predicate directly while expanding root variables. Define the async flag, pool provider, and Helix credential group together so same-block variable lookup cannot omit monitor prerequisites. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- azure-pipelines.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5fa85dc4c40..fefb38dd0de 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,12 +15,17 @@ parameters: variables: # This entrypoint has one implicit stage. Keep asynchronous sends paired with its monitor, # and leave public/PR runs and manual runs without selected jobs on the synchronous default. - - name: EnableHelixJobMonitor - value: ${{ and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest'), or(ne(variables['Build.Reason'], 'Manual'), parameters.runPublicJobs, parameters.runPrivateJobs, parameters.runScheduledPrivateJobs)) }} - - ${{ if eq(variables.EnableHelixJobMonitor, 'true') }}: + # Use the inputs directly here: a variable defined in this block is not yet + # available to conditions within the same block during template expansion. + - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest'), or(ne(variables['Build.Reason'], 'Manual'), parameters.runPublicJobs, parameters.runPrivateJobs, parameters.runScheduledPrivateJobs)) }}: + - name: EnableHelixJobMonitor + value: true - template: /eng/common/templates/variables/pool-providers.yml # The standalone monitor does not inherit the submitter jobs' variable groups. - group: DotNet-HelixApi-Access + - ${{ else }}: + - name: EnableHelixJobMonitor + value: false resources: containers: From b246c000b9d52f71678770e3e5bfdcf4601f4d7d Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Thu, 10 Sep 2026 23:15:44 -0700 Subject: [PATCH 3/9] Complete SDK Helix monitor public support and Linux startup Enable anonymous monitoring for selected public and PR jobs, keep internal credentials guarded, and bound the monitor to six hours without changing sender or work-item timeouts. Restore the Arcade bootstrap executable bit for Linux tool restore. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- azure-pipelines.yml | 17 +++++++++-------- docs/sdk-scenarios.md | 30 +++++++++++++++++++----------- eng/common/dotnet.sh | 0 3 files changed, 28 insertions(+), 19 deletions(-) mode change 100644 => 100755 eng/common/dotnet.sh diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fefb38dd0de..e4d58c6f913 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,16 +13,17 @@ parameters: default: false variables: - # This entrypoint has one implicit stage. Keep asynchronous sends paired with its monitor, - # and leave public/PR runs and manual runs without selected jobs on the synchronous default. + # This entrypoint has one implicit stage. Keep asynchronous sends paired with its monitor. + # Public runs always select correctness jobs; internal runs use the existing job selectors. # Use the inputs directly here: a variable defined in this block is not yet # available to conditions within the same block during template expansion. - - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest'), or(ne(variables['Build.Reason'], 'Manual'), parameters.runPublicJobs, parameters.runPrivateJobs, parameters.runScheduledPrivateJobs)) }}: + - ${{ if or(eq(variables['System.TeamProject'], 'public'), and(eq(variables['System.TeamProject'], 'internal'), or(notin(variables['Build.Reason'], 'PullRequest', 'Manual'), eq(variables['Build.CronSchedule.DisplayName'], 'Every 12 hours build'), parameters.runPublicJobs, parameters.runPrivateJobs, parameters.runScheduledPrivateJobs))) }}: - name: EnableHelixJobMonitor value: true - template: /eng/common/templates/variables/pool-providers.yml - # The standalone monitor does not inherit the submitter jobs' variable groups. - - group: DotNet-HelixApi-Access + - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')) }}: + # The standalone monitor does not inherit the submitter jobs' variable groups. + - group: DotNet-HelixApi-Access - ${{ else }}: - name: EnableHelixJobMonitor value: false @@ -86,7 +87,7 @@ jobs: - ${{ if eq(variables.EnableHelixJobMonitor, 'true') }}: - template: /eng/common/core-templates/job/helix-job-monitor.yml parameters: - # Allow the existing 320-minute submitter budget plus six-hour Helix work items. - timeoutInMinutes: 720 - helixAccessToken: $(HelixApiAccessToken) + timeoutInMinutes: 360 + ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')) }}: + helixAccessToken: $(HelixApiAccessToken) allowNoHelixJobs: false diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index 34d1630292c..a84d4e4d56e 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -11,17 +11,25 @@ The repository's [azure-pipelines.yml](../azure-pipelines.yml) entrypoint uses benchmark jobs. It backs the internal `dotnet-performance` pipeline (definition 306) and the public `performance-ci` pipeline (definition 38). -Internal non-PR runs with selected jobs submit asynchronously and use the standalone -Helix Job Monitor in the same implicit stage. The monitor runs alongside submitters, -waits for their Helix work items, and reports failures to Azure DevOps. Public and PR -runs remain synchronous. The existing manual job-selection parameters and schedules -are unchanged; a manual run with no jobs selected does not start a monitor. A monitored -run with no Helix jobs fails rather than silently succeeding. - -The entrypoint imports `DotNet-HelixApi-Access` only when the monitor is enabled, so -the standalone job receives its own credential rather than depending on a submitter's -job-scoped variables. Its tool is restored from `.config/dotnet-tools.json` in its own -checkout; `eng/Version.Details.xml` tracks that pin alongside the Helix SDK. +Public runs (including PRs) and internal runs with selected jobs submit asynchronously +and use the standalone Helix Job Monitor in the same implicit stage. The monitor runs +alongside submitters, waits for their Helix work items, and reports failures to Azure +DevOps. The existing manual job-selection parameters and schedules are unchanged: +public runs always select correctness jobs, while an internal manual run with no jobs +selected does not start a monitor. A monitored run with no Helix jobs fails rather than +silently succeeding. + +The monitor job has a six-hour timeout (355 minutes for the tool, leaving five minutes +for it to exit). Existing 320-minute submitter limits and Helix work-item timeouts are +unchanged. These limits start when the respective job runs; they are not a whole-run +wall-clock deadline that includes time waiting for agents. + +Only monitored internal non-PR runs import `DotNet-HelixApi-Access` and pass its token +to the standalone job. Public and PR monitors use anonymous Helix access, not an +unresolved private-token variable. Azure DevOps timeline access and result reporting +still use the job's `System.AccessToken`. The tool is restored from +`.config/dotnet-tools.json` in its own checkout; `eng/Version.Details.xml` tracks that +pin alongside the Helix SDK. Submitter build logs still use the existing `Logs_*` pipeline artifacts. Benchmark results and diagnostics are still uploaded from the Helix work items to PerfLab and diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh old mode 100644 new mode 100755 From c813e894947b143f536e6b6075103a8f9b2af7c9 Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Fri, 11 Sep 2026 05:28:40 -0700 Subject: [PATCH 4/9] Keep SDK performance matrix legs distinct in Helix monitoring Prefix monitor test-run identities with the matrix-expanded agent job name so independent channels sharing a phase and queue are not treated as retries. Retain stable identities for job and stage retries and scope the naming change to monitored SDK flows. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- azure-pipelines.yml | 3 +++ docs/sdk-scenarios.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e4d58c6f913..63f75e9196e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,6 +20,9 @@ variables: - ${{ if or(eq(variables['System.TeamProject'], 'public'), and(eq(variables['System.TeamProject'], 'internal'), or(notin(variables['Build.Reason'], 'PullRequest', 'Manual'), eq(variables['Build.CronSchedule.DisplayName'], 'Every 12 hours build'), parameters.runPublicJobs, parameters.runPrivateJobs, parameters.runScheduledPrivateJobs))) }}: - name: EnableHelixJobMonitor value: true + # Matrix legs share a phase and queue. Keep their monitor identities distinct across channels. + - name: TestRunNamePrefix + value: '$(Agent.JobName)_' - template: /eng/common/templates/variables/pool-providers.yml - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')) }}: # The standalone monitor does not inherit the submitter jobs' variable groups. diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index a84d4e4d56e..b9d04d56046 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -31,6 +31,10 @@ still use the job's `System.AccessToken`. The tool is restored from `.config/dotnet-tools.json` in its own checkout; `eng/Version.Details.xml` tracks that pin alongside the Helix SDK. +Monitor test-run names include the matrix-expanded job display name. This keeps +different channels in the same phase and Helix queue independent while preserving a +stable identity when the same leg is retried. + Submitter build logs still use the existing `Logs_*` pipeline artifacts. Benchmark results and diagnostics are still uploaded from the Helix work items to PerfLab and Helix, respectively. The monitor reports test results and links to Helix consoles; it From dd921d3318c815b24393a94660dd2584c4789d54 Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Fri, 11 Sep 2026 12:27:27 -0700 Subject: [PATCH 5/9] Always include the SDK performance Helix monitor Keep asynchronous submission, the pool provider, matrix identity prefix, and monitor parameter block unconditional. Use anonymous Helix credentials outside internal non-PR runs, and allow legitimate no-workload stages without changing workload selection or timeout budgets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- azure-pipelines.yml | 38 +++++++++++++++----------------------- docs/sdk-scenarios.md | 24 +++++++++++++----------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 63f75e9196e..b36691ecbce 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,22 +14,15 @@ parameters: variables: # This entrypoint has one implicit stage. Keep asynchronous sends paired with its monitor. - # Public runs always select correctness jobs; internal runs use the existing job selectors. - # Use the inputs directly here: a variable defined in this block is not yet - # available to conditions within the same block during template expansion. - - ${{ if or(eq(variables['System.TeamProject'], 'public'), and(eq(variables['System.TeamProject'], 'internal'), or(notin(variables['Build.Reason'], 'PullRequest', 'Manual'), eq(variables['Build.CronSchedule.DisplayName'], 'Every 12 hours build'), parameters.runPublicJobs, parameters.runPrivateJobs, parameters.runScheduledPrivateJobs))) }}: - - name: EnableHelixJobMonitor - value: true - # Matrix legs share a phase and queue. Keep their monitor identities distinct across channels. - - name: TestRunNamePrefix - value: '$(Agent.JobName)_' - - template: /eng/common/templates/variables/pool-providers.yml - - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')) }}: - # The standalone monitor does not inherit the submitter jobs' variable groups. - - group: DotNet-HelixApi-Access - - ${{ else }}: - - name: EnableHelixJobMonitor - value: false + - name: EnableHelixJobMonitor + value: true + # Matrix legs share a phase and queue. Keep their monitor identities distinct across channels. + - name: TestRunNamePrefix + value: '$(Agent.JobName)_' + - template: /eng/common/templates/variables/pool-providers.yml + - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')) }}: + # The standalone monitor does not inherit the submitter jobs' variable groups. + - group: DotNet-HelixApi-Access resources: containers: @@ -87,10 +80,9 @@ jobs: ${{ if parameters.onlySanityCheck }}: onlySanityCheck: true - - ${{ if eq(variables.EnableHelixJobMonitor, 'true') }}: - - template: /eng/common/core-templates/job/helix-job-monitor.yml - parameters: - timeoutInMinutes: 360 - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')) }}: - helixAccessToken: $(HelixApiAccessToken) - allowNoHelixJobs: false + - template: /eng/common/core-templates/job/helix-job-monitor.yml + parameters: + timeoutInMinutes: 360 + helixAccessToken: ${{ iif(and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')), '$(HelixApiAccessToken)', '') }} + # Manual runs can legitimately select no workloads. + allowNoHelixJobs: true diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index b9d04d56046..f3312c9510d 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -11,23 +11,25 @@ The repository's [azure-pipelines.yml](../azure-pipelines.yml) entrypoint uses benchmark jobs. It backs the internal `dotnet-performance` pipeline (definition 306) and the public `performance-ci` pipeline (definition 38). -Public runs (including PRs) and internal runs with selected jobs submit asynchronously -and use the standalone Helix Job Monitor in the same implicit stage. The monitor runs -alongside submitters, waits for their Helix work items, and reports failures to Azure -DevOps. The existing manual job-selection parameters and schedules are unchanged: -public runs always select correctness jobs, while an internal manual run with no jobs -selected does not start a monitor. A monitored run with no Helix jobs fails rather than -silently succeeding. +The entrypoint always enables asynchronous submission and includes the standalone +Helix Job Monitor, its pool provider, and its parameters, including on public and PR +runs. The monitor runs alongside submitters in the same implicit stage, waits for +their Helix work items, and reports failures to Azure DevOps. The existing manual +job-selection parameters and schedules are unchanged: public runs always select +correctness jobs, while internal manual runs can select no workloads. Such runs still +include the monitor and may succeed without Helix jobs (`allowNoHelixJobs: true`). +This permits an empty stage; it does not suppress submitter or work-item failures. The monitor job has a six-hour timeout (355 minutes for the tool, leaving five minutes for it to exit). Existing 320-minute submitter limits and Helix work-item timeouts are unchanged. These limits start when the respective job runs; they are not a whole-run wall-clock deadline that includes time waiting for agents. -Only monitored internal non-PR runs import `DotNet-HelixApi-Access` and pass its token -to the standalone job. Public and PR monitors use anonymous Helix access, not an -unresolved private-token variable. Azure DevOps timeline access and result reporting -still use the job's `System.AccessToken`. The tool is restored from +Only internal non-PR runs import `DotNet-HelixApi-Access`. The always-present token +parameter uses a compile-time expression to select its token macro for those runs +and an empty string otherwise. Public and PR monitors therefore use anonymous Helix +access, not an unresolved private-token variable. Azure DevOps timeline access and +result reporting still use the job's `System.AccessToken`. The tool is restored from `.config/dotnet-tools.json` in its own checkout; `eng/Version.Details.xml` tracks that pin alongside the Helix SDK. From d7d958ae317d34c91ff9b8783771335f88f9053a Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Mon, 14 Sep 2026 09:55:47 -0700 Subject: [PATCH 6/9] Provide SDK monitor credentials for internal PR runs Align monitor credential eligibility with dotnet/runtime#133885: import the Helix group and select its token for the internal project regardless of build reason. Public runs, including public PRs, remain anonymous. Preserve workload selection and monitor settings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- azure-pipelines.yml | 4 ++-- docs/sdk-scenarios.md | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b36691ecbce..49f99c96123 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,7 +20,7 @@ variables: - name: TestRunNamePrefix value: '$(Agent.JobName)_' - template: /eng/common/templates/variables/pool-providers.yml - - ${{ if and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(variables['System.TeamProject'], 'internal') }}: # The standalone monitor does not inherit the submitter jobs' variable groups. - group: DotNet-HelixApi-Access @@ -83,6 +83,6 @@ jobs: - template: /eng/common/core-templates/job/helix-job-monitor.yml parameters: timeoutInMinutes: 360 - helixAccessToken: ${{ iif(and(eq(variables['System.TeamProject'], 'internal'), ne(variables['Build.Reason'], 'PullRequest')), '$(HelixApiAccessToken)', '') }} + helixAccessToken: ${{ iif(eq(variables['System.TeamProject'], 'internal'), '$(HelixApiAccessToken)', '') }} # Manual runs can legitimately select no workloads. allowNoHelixJobs: true diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index f3312c9510d..7516cf7bff3 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -25,11 +25,12 @@ for it to exit). Existing 320-minute submitter limits and Helix work-item timeou unchanged. These limits start when the respective job runs; they are not a whole-run wall-clock deadline that includes time waiting for agents. -Only internal non-PR runs import `DotNet-HelixApi-Access`. The always-present token -parameter uses a compile-time expression to select its token macro for those runs -and an empty string otherwise. Public and PR monitors therefore use anonymous Helix -access, not an unresolved private-token variable. Azure DevOps timeline access and -result reporting still use the job's `System.AccessToken`. The tool is restored from +Internal runs, including internal PR runs, import `DotNet-HelixApi-Access`. The +always-present token parameter uses a compile-time expression to select its token +macro for the internal project and an empty string otherwise. Public runs, including +public PR runs, therefore use anonymous Helix access, not an unresolved private-token +variable. Azure DevOps timeline access and result reporting still use the job's +`System.AccessToken`. The tool is restored from `.config/dotnet-tools.json` in its own checkout; `eng/Version.Details.xml` tracks that pin alongside the Helix SDK. From 07aebb250a15a5b4ce8a4c87c4faa21d50ef46ce Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Mon, 14 Sep 2026 11:00:43 -0700 Subject: [PATCH 7/9] Fail SDK performance runs without Helix jobs Keep the monitor unconditional but reject zero-job discovery, including internal manual runs with all workload selectors false. Preserve workload selection, credential scope, timeout budgets, and matrix identities. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- azure-pipelines.yml | 3 +-- docs/sdk-scenarios.md | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 49f99c96123..523ebe58011 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -84,5 +84,4 @@ jobs: parameters: timeoutInMinutes: 360 helixAccessToken: ${{ iif(eq(variables['System.TeamProject'], 'internal'), '$(HelixApiAccessToken)', '') }} - # Manual runs can legitimately select no workloads. - allowNoHelixJobs: true + allowNoHelixJobs: false diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index 7516cf7bff3..9c936d93a30 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -16,9 +16,10 @@ Helix Job Monitor, its pool provider, and its parameters, including on public an runs. The monitor runs alongside submitters in the same implicit stage, waits for their Helix work items, and reports failures to Azure DevOps. The existing manual job-selection parameters and schedules are unchanged: public runs always select -correctness jobs, while internal manual runs can select no workloads. Such runs still -include the monitor and may succeed without Helix jobs (`allowNoHelixJobs: true`). -This permits an empty stage; it does not suppress submitter or work-item failures. +correctness jobs. Internal manual runs must select workloads; leaving all three +job-selection flags false still includes the monitor and fails because no Helix jobs +were submitted (`allowNoHelixJobs: false`). Submitter and work-item failures also +continue to fail the run. The monitor job has a six-hour timeout (355 minutes for the tool, leaving five minutes for it to exit). Existing 320-minute submitter limits and Helix work-item timeouts are From 70dd378ffe64e5d8a670408ebed1b41fbdf10928 Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Tue, 15 Sep 2026 09:50:47 -0700 Subject: [PATCH 8/9] Forward the SDK Helix monitor token directly Match the accepted runtime pipeline convention by passing HelixApiAccessToken without a compile-time iif. Keep the credential group internal-only and document the public empty-value contract without assuming unresolved macros become empty. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- azure-pipelines.yml | 2 +- docs/sdk-scenarios.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 523ebe58011..9a4cdcf2bac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -83,5 +83,5 @@ jobs: - template: /eng/common/core-templates/job/helix-job-monitor.yml parameters: timeoutInMinutes: 360 - helixAccessToken: ${{ iif(eq(variables['System.TeamProject'], 'internal'), '$(HelixApiAccessToken)', '') }} + helixAccessToken: $(HelixApiAccessToken) allowNoHelixJobs: false diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index 9c936d93a30..c98f2f44f99 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -27,10 +27,11 @@ unchanged. These limits start when the respective job runs; they are not a whole wall-clock deadline that includes time waiting for agents. Internal runs, including internal PR runs, import `DotNet-HelixApi-Access`. The -always-present token parameter uses a compile-time expression to select its token -macro for the internal project and an empty string otherwise. Public runs, including -public PR runs, therefore use anonymous Helix access, not an unresolved private-token -variable. Azure DevOps timeline access and result reporting still use the job's +monitor forwards `$(HelixApiAccessToken)` directly, matching the shared pipeline +convention. Public runs, including public PR runs, rely on the public pipeline's +empty value for this variable to use anonymous Helix access; the internal credential +group is not imported there. An undefined macro is not a substitute for that empty +value. Azure DevOps timeline access and result reporting still use the job's `System.AccessToken`. The tool is restored from `.config/dotnet-tools.json` in its own checkout; `eng/Version.Details.xml` tracks that pin alongside the Helix SDK. From 75530bae17e2a1beb07dce81ba9da3bb36f9f641 Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Tue, 15 Sep 2026 09:52:58 -0700 Subject: [PATCH 9/9] Clarify public Helix access with direct token forwarding Document the established forwarding convention without asserting an unverified empty-variable requirement. Read-only checks confirmed matching public job and work-item visibility with no auth and a literal unresolved-token header. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/sdk-scenarios.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index c98f2f44f99..c09f165f198 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -28,10 +28,10 @@ wall-clock deadline that includes time waiting for agents. Internal runs, including internal PR runs, import `DotNet-HelixApi-Access`. The monitor forwards `$(HelixApiAccessToken)` directly, matching the shared pipeline -convention. Public runs, including public PR runs, rely on the public pipeline's -empty value for this variable to use anonymous Helix access; the internal credential -group is not imported there. An undefined macro is not a substitute for that empty -value. Azure DevOps timeline access and result reporting still use the job's +convention. Internal runs use the imported credential. Public runs, including public +PR runs, do not import that group and use public Helix access. Direct forwarding +does not change how Azure DevOps expands variables or how the SDK handles tokens. +Azure DevOps timeline access and result reporting still use the job's `System.AccessToken`. The tool is restored from `.config/dotnet-tools.json` in its own checkout; `eng/Version.Details.xml` tracks that pin alongside the Helix SDK.