Skip to content

Skip whole CI legs for documentation/.github-only PRs#14384

Open
jankratochvilcz wants to merge 6 commits into
mainfrom
skip-ci-jobs-for-docs-only
Open

Skip whole CI legs for documentation/.github-only PRs#14384
jankratochvilcz wants to merge 6 commits into
mainfrom
skip-ci-jobs-for-docs-only

Conversation

@jankratochvilcz

Copy link
Copy Markdown
Contributor

Problem

The previous docs-only optimization (#14371) gated the heavy steps of each CI leg with condition: eq(variables.onlyDocChanged, 0). That still allocated an agent for every leg — a docs-only PR would sit waiting for machine allocation across all legs (Windows Full/Core, Linux, macOS, MT, Full Release, Code Coverage, Source-Build) before skipping the actual build. As seen on the mock PR, a leg can get stuck waiting on the pool even though it does nothing.

Fix

Gate at the job level so a docs-only PR never requests an agent for the heavy legs:

  • Add one lightweight EvaluateDocumentationOnlyChange job that runs the existing detection template once and exposes onlyDocChanged as an output variable.
  • Every heavy job in .vsts-dotnet-ci.yml now dependsOn it and carries a job-level condition: eq(dependencies.EvaluateDocumentationOnlyChange.outputs['CheckDocumentationOnlyChange.onlyDocChanged'], '0'). Skipped jobs allocate no machine.
  • The templated source-build leg is gated the same way via jobProperties (no changes to Arcade-managed eng/common).
  • azure-pipelines/quarantine.yml gets the same treatment, but keeps the existing semantics: non-PR runs (rolling/batched and the daily schedule) always run, so the quarantined-test signal on main is unaffected — only docs-only PR runs are skipped.

Net effect for a documentation/.github-only PR: only the ~seconds-long detection job runs; all build/test legs show as skipped without ever waiting on a pool. Real code PRs are unaffected (this PR itself touches pipeline YAML, so it runs full CI).

The per-job detection step is intentionally retained inside each heavy job so the existing per-step conditions and the -onlyDocChanged script argument keep working unchanged when a leg does run.

true

Gating the heavy build/test steps with a per-step condition still allocated an
agent for every leg -- a docs-only PR would sit waiting for machine allocation
across all legs before skipping the build. This introduces a single lightweight
`EvaluateDocumentationOnlyChange` job that computes `onlyDocChanged` once and
exposes it as an output variable; every heavy job in .vsts-dotnet-ci.yml and
azure-pipelines/quarantine.yml now `dependsOn` it and carries a job-level
`condition`, so for a documentation/.github-only PR the entire leg is skipped and
no agent is ever requested. The source-build leg is gated via `jobProperties`.

Quarantine legs keep running on non-PR (rolling/batched and scheduled) builds so
the quarantined-test signal on main is unaffected.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95de2e0b-cc2f-47fc-a95a-3d66f8cd1d1a

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes MSBuild’s Azure Pipelines CI for documentation- and .github-only pull requests by moving the “docs-only change” detection to a single lightweight gating job and using job-level conditions to skip allocating agents for heavy CI legs.

Changes:

  • Add an EvaluateDocumentationOnlyChange job that runs the existing check-documentation-only-change.yml template once and exposes onlyDocChanged as a job output.
  • Gate all heavy jobs in .vsts-dotnet-ci.yml on the output variable so docs-only PRs skip entire jobs (no agent allocation).
  • Apply the same gating pattern to the quarantined-test pipeline (azure-pipelines/quarantine.yml) while still always running non-PR executions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
azure-pipelines/quarantine.yml Adds a lightweight evaluation job and gates quarantine jobs so docs-only PRs skip agent allocation while non-PR runs still execute.
.vsts-dotnet-ci.yml Introduces a shared docs-only evaluation job and applies job-level gating across all heavy CI legs, including the templated source-build leg via jobProperties.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Overall: Good approach to skip CI for doc-only PRs at the job level. Three issues found:

High: Incorrect diff detection (check-documentation-only-change.yml)

git diff HEAD HEAD~1 only checks the last commit, not the full PR diff. Multi-commit PRs can be misclassified. Use the PR target branch diff instead.

Medium: Fail-closed gate (.vsts-dotnet-ci.yml)

If the gate job fails, all downstream jobs silently skip (output variable unset ≠ '0'). This is a fail-closed design that can mask CI failures. Consider fail-open conditions.

Low: Quarantine pool inconsistency (quarantine.yml)

Missing internal project pool configuration.

Verified OK

  • source-build.yml template does support jobProperties on the platform object ✓
  • Quarantine conditions correctly allow non-PR runs via or(ne(variables['Build.Reason'], 'PullRequest'), ...)
  • CodeCoverage job's and(succeeded(), ...) correctly blocks if gate fails ✓

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Expert Code Review (on open) for #14384 · 58.8 AIC · ⌖ 7.66 AIC · ⊞ 4.8K

Comment thread .vsts-dotnet-ci.yml
Comment thread azure-pipelines/quarantine.yml
@jankratochvilcz
jankratochvilcz requested a deployment to copilot-pat-pool July 15, 2026 17:52 — with GitHub Actions Abandoned
jankratochvilcz and others added 2 commits July 16, 2026 00:01
Address review feedback: if the EvaluateDocumentationOnlyChange gate job fails
(shallow clone, git/agent issue), its output variable is unset, which under the
previous conditions caused every downstream job to skip -- a transient gate
failure would report the whole PR CI green (fail-closed, masking real breakage).

Every gated job condition now runs the leg when the gate did not succeed
(`not(succeeded('EvaluateDocumentationOnlyChange'))`), so a gate failure runs the
full matrix instead of silently skipping it. Legs are skipped only when the gate
succeeded AND classified the change as documentation/.github-only.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95de2e0b-cc2f-47fc-a95a-3d66f8cd1d1a
Address review feedback on diff detection. For AzDO PR validation builds the
checkout is the merge ref refs/pull/<N>/merge, whose first parent (HEAD~1) is
the base-branch tip, so `git diff HEAD HEAD~1` already yields the full cumulative
PR diff regardless of commit count. Documented that reasoning inline.

Additionally wrap detection in error handling that defaults onlyDocChanged=0
(run full CI) on any git failure or empty diff. A detection problem can now never
skip CI for a code change -- it can only ever over-run, which is the safe
direction -- complementing the job-level fail-open conditions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95de2e0b-cc2f-47fc-a95a-3d66f8cd1d1a
@jankratochvilcz

Copy link
Copy Markdown
Contributor Author

Addressed the three review points in 71c919f + a929820:

High: Incorrect diff detectiongit diff HEAD HEAD~1 only checks the last commit, not the full PR diff. Multi-commit PRs can be misclassified.

For AzDO PR validation builds the checkout is the GitHub-generated merge ref refs/pull/<N>/merge. That merge commit's first parent (HEAD~1) is the tip of the PR's base branch, so git diff HEAD HEAD~1 is the full cumulative PR diff regardless of commit count — not just the last commit. I validated this empirically in both directions (a docs-only mock PR skipped all legs; the YAML change here ran the full matrix). I've documented the reasoning inline in the template.

On top of that, I added detection-level fail-open: detection now defaults to onlyDocChanged=0 (run full CI) on any git error or an empty diff. So even in a hypothetical checkout where HEAD~1 were ambiguous, the result is to run CI, never to skip it for a code change — the safe direction.

Medium: Fail-closed gate — consider fail-open conditions.

Done — every gated job condition now runs the leg when the gate did not succeed (not(succeeded('EvaluateDocumentationOnlyChange'))). A transient gate failure runs the full matrix instead of silently reporting green.

Low: Quarantine pool inconsistency — missing internal project pool configuration.

Intentional — azure-pipelines/quarantine.yml is public-only throughout (every job targets NetCore-Public / macOS-latest); the conditional public/internal selection lives in the separate dual-pipeline .vsts-dotnet-ci.yml. Matching the surrounding jobs keeps it consistent.

@rainersigwald

Copy link
Copy Markdown
Member
  • Add one lightweight EvaluateDocumentationOnlyChange job that runs the existing detection template once and exposes onlyDocChanged as an output variable.

In the past, scheduling pressure has meant that this strategy slowed down builds that need to run full CI dramatically:

  1. queue the check job
  2. wait for an available runner
  3. queue the real jobs
  4. wait again from the back of the queue again

We're actually seeing this already, right now: the check job has been waiting 13 minutes and is still 203 in the queue, and all the other jobs have not even been considered. So I expect this PR check to spend at least 25 minutes queuing.

@jankratochvilcz

Copy link
Copy Markdown
Contributor Author

I agree this is not great @rainersigwald, trying to think other ways we could drop the entire jobs....

jankratochvilcz and others added 3 commits July 16, 2026 11:17
The fail-open gate condition was copied verbatim onto ~10 jobs across both
pipelines. Azure Pipelines cannot reuse a whole condition via $(var) or
${{ variables.x }} (those insert a string that isn't evaluated), but a
compile-time parameter reference IS substituted as expression text before
evaluation.

Author the expression once as a `runUnlessDocumentationOnly` pipeline parameter
in each file and reference it via ${{ parameters.runUnlessDocumentationOnly }}
on every gated job (CodeCoverage keeps its and(succeeded(), ...) wrapper). No
behavioral change -- the compiled conditions are identical to before.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95de2e0b-cc2f-47fc-a95a-3d66f8cd1d1a
The EvaluateDocumentationOnlyChange gate is always-on (it must report the
required msbuild-pr check and it needs the runtime changed-file list), so it
cannot be removed without a server-side branch-policy path filter. Running it on
the self-hosted NetCore-Public pool, however, added it to the very queue it
exists to relieve -- every PR paid a self-hosted allocation for the gate, and
code PRs serialized behind it.

Move the gate to the Microsoft-hosted pool (vmImage: ubuntu-latest), which is
free and uncongested for this public project. Net effect: a docs-only PR now
allocates zero self-hosted agents (all heavy legs skipped, gate on hosted), and a
code PR waits only for a short hosted job before the heavy legs start. Documented
the zero-job branch-policy path-filter alternative inline for maintainers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95de2e0b-cc2f-47fc-a95a-3d66f8cd1d1a
Running the gate on the Microsoft-hosted pool for public PR builds is a security
concern (untrusted fork PR code on hosted agents). Restore the gate job in both
pipelines to the self-hosted NetCore-Public pool, matching every other leg.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95de2e0b-cc2f-47fc-a95a-3d66f8cd1d1a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants