ci: skip build/test/scan jobs for docs-only changes#180
ci: skip build/test/scan jobs for docs-only changes#180MCGPPeters wants to merge 1 commit intomainfrom
Conversation
Add a detect-changes job to cd, e2e, codeql, and pr-validation workflows that detects whether a push/PR contains only documentation changes (.squad/, docs/, instructions/, *.md, LICENSE, etc.). When only docs change: - build job (cd.yml) is skipped - saves full dotnet build + 8 test suites - e2e job (e2e.yml) is skipped - saves up to 120 minutes of Playwright tests - analyze job (codeql.yml) is skipped for push/PR; schedule always runs - lint-check, security-scan, template-smoke-test, bundle-size-check, check-todos (pr-validation.yml) are all skipped Skipped jobs post skipped status which satisfies required branch protection checks. pr-validation-summary uses always() && !failure() so it still runs when jobs are skipped, maintaining the required gate check. For non-required workflows (semgrep, trivy, zap-baseline), paths-ignore is added to both pull_request and push triggers. secrets-scan.yml is intentionally unchanged - gitleaks should scan all commits regardless of file type.
There was a problem hiding this comment.
Pull request overview
This PR adds “docs-only change” detection to CI so documentation-only pull requests don’t trigger long-running build/test/security jobs, reducing wasted CI time while keeping required workflows green.
Changes:
- Add
paths-ignorefilters to non-required security workflows (Semgrep, Trivy, ZAP baseline) so the workflows don’t start for docs-only changes. - Add a
detect-changesjob to required workflows (CD, E2E, CodeQL, PR validation) and gate expensive jobs based on adocs_onlyoutput. - Adjust PR validation summary gating so it can still run when upstream jobs are skipped (docs-only path).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/zap-baseline.yml | Add paths-ignore so ZAP baseline doesn’t run on docs-only changes. |
| .github/workflows/trivy.yml | Add paths-ignore so Trivy doesn’t run on docs-only changes. |
| .github/workflows/semgrep.yml | Add paths-ignore so Semgrep doesn’t run on docs-only changes. |
| .github/workflows/pr-validation.yml | Add docs-only detection job and gate multiple expensive validation jobs; update summary job condition. |
| .github/workflows/e2e.yml | Add docs-only detection job and gate the 120-minute E2E job. |
| .github/workflows/codeql.yml | Add docs-only detection job and gate CodeQL analysis except on scheduled runs. |
| .github/workflows/cd.yml | Add docs-only detection job and gate build/test/pack/publish work. |
| # Detect whether this is a docs-only PR to skip expensive code-quality jobs | ||
| detect-changes: | ||
| name: Detect Changes | ||
| runs-on: ubuntu-latest | ||
| if: github.event.pull_request.draft == false | ||
| outputs: | ||
| docs_only: ${{ steps.check.outputs.docs_only }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Check for non-docs changes | ||
| id: check | ||
| run: | | ||
| CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | ||
| NON_DOCS=$(echo "$CHANGED" | grep -vE '^(docs/|\.squad/|\.github/instructions/|README|CHANGELOG|CONTRIBUTING|SECURITY|LICENSE|BENCHMARK_PR_STATUS|.*\.md$)' | grep -v '^$' || true) | ||
| if [ -z "$NON_DOCS" ]; then | ||
| echo "docs_only=true" >> $GITHUB_OUTPUT | ||
| echo "📚 Docs-only PR — lint, security scan, and template smoke test will be skipped" | ||
| echo "$CHANGED" | head -20 | ||
| else | ||
| echo "docs_only=false" >> $GITHUB_OUTPUT | ||
| echo "🔨 Code changes detected — running full PR validation" | ||
| echo "$NON_DOCS" | head -20 | ||
| fi |
There was a problem hiding this comment.
jobs: children are inconsistently indented: detect-changes is indented deeper than the other job keys (e.g., validate-pr-title). YAML requires sibling keys to be at the same indentation level; as written this is likely to make the workflow invalid or nest detect-changes incorrectly. Align detect-changes to the same indentation as the other jobs (two spaces under jobs:).
| # Detect whether this is a docs-only PR to skip expensive code-quality jobs | |
| detect-changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| outputs: | |
| docs_only: ${{ steps.check.outputs.docs_only }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for non-docs changes | |
| id: check | |
| run: | | |
| CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | |
| NON_DOCS=$(echo "$CHANGED" | grep -vE '^(docs/|\.squad/|\.github/instructions/|README|CHANGELOG|CONTRIBUTING|SECURITY|LICENSE|BENCHMARK_PR_STATUS|.*\.md$)' | grep -v '^$' || true) | |
| if [ -z "$NON_DOCS" ]; then | |
| echo "docs_only=true" >> $GITHUB_OUTPUT | |
| echo "📚 Docs-only PR — lint, security scan, and template smoke test will be skipped" | |
| echo "$CHANGED" | head -20 | |
| else | |
| echo "docs_only=false" >> $GITHUB_OUTPUT | |
| echo "🔨 Code changes detected — running full PR validation" | |
| echo "$NON_DOCS" | head -20 | |
| fi | |
| # Detect whether this is a docs-only PR to skip expensive code-quality jobs | |
| detect-changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| outputs: | |
| docs_only: ${{ steps.check.outputs.docs_only }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for non-docs changes | |
| id: check | |
| run: | | |
| CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) | |
| NON_DOCS=$(echo "$CHANGED" | grep -vE '^(docs/|\.squad/|\.github/instructions/|README|CHANGELOG|CONTRIBUTING|SECURITY|LICENSE|BENCHMARK_PR_STATUS|.*\.md$)' | grep -v '^$' || true) | |
| if [ -z "$NON_DOCS" ]; then | |
| echo "docs_only=true" >> $GITHUB_OUTPUT | |
| echo "📚 Docs-only PR — lint, security scan, and template smoke test will be skipped" | |
| echo "$CHANGED" | head -20 | |
| else | |
| echo "docs_only=false" >> $GITHUB_OUTPUT | |
| echo "🔨 Code changes detected — running full PR validation" | |
| echo "$NON_DOCS" | head -20 | |
| fi |
| # always() + !failure() = run even when dependencies were skipped (docs-only), | ||
| # but not when any dependency explicitly failed. | ||
| if: always() && !cancelled() && !failure() && github.event.pull_request.draft == false | ||
| needs: [validate-pr-title, validate-pr-description, check-pr-size, lint-check, security-scan, template-smoke-test, bundle-size-check, check-todos] |
There was a problem hiding this comment.
With the updated if: this summary job now runs even when lint-check/security-scan/template-smoke-test/etc are skipped (docs-only). The subsequent summary output still prints "✅" lines for checks that may not have executed, which can mislead reviewers and makes CI output harder to interpret. Consider including detect-changes in needs (or using needs.<job>.result) and printing an explicit "skipped (docs-only)" status for gated jobs.
What
Add docs-only change detection to 7 CI workflows so that PRs touching only documentation files (.squad/, docs/, instructions/, *.md, LICENSE, etc.) skip all expensive code-quality checks.
Why
Squad update PRs and docs-only changes were triggering the full CI pipeline — up to 120+ minutes of build, test, and scan time — for changes that cannot possibly break code. This wasted CI resources and slowed docs-only merges.
How
Two patterns are used depending on whether the workflow is a required branch protection check:
detect-changes job (for required checks — cd, e2e, codeql, pr-validation): A small first job diffs changed file paths against a docs-only regex and outputs
docs_only=true/false. Expensive jobs useneeds: [detect-changes]+if: ...docs_only == 'false'. Skipped jobs post "skipped" status which satisfies required branch protection rules without blocking the PR.paths-ignore (for non-required scans — semgrep, trivy, zap-baseline): Added to both
pull_requestandpushtriggers. The workflow simply doesn't start for docs-only changes.secrets-scan.ymlis intentionally unchanged — gitleaks should scan all commits regardless of file type.Workflows changed
cd.yml— detect-changes job gates the build job (dotnet build + 8 test suites)e2e.yml— detect-changes job gates the e2e job (120-min Playwright suite)codeql.yml— detect-changes gates the analyze job on push/PR; weekly schedule always runspr-validation.yml— detect-changes gates lint-check, security-scan, template-smoke-test, bundle-size-check, check-todos; pr-validation-summary updated toalways() && !failure()so it runs on skipped-job pathssemgrep.yml— paths-ignore on both triggerstrivy.yml— paths-ignore on both triggerszap-baseline.yml— paths-ignore on both triggersTesting
The detect-changes job logic uses standard
git diff --name-onlyand a regex filter. The skip path is exercised naturally by any docs-only PR (like squad update PRs).