Skip to content

fix(ci): make docs-only PRs mergeable (gate required e2e/smoke per-job)#334

Open
Timofei Larkin (lllamnyp) wants to merge 1 commit into
mainfrom
fix/docs-pr-required-checks
Open

fix(ci): make docs-only PRs mergeable (gate required e2e/smoke per-job)#334
Timofei Larkin (lllamnyp) wants to merge 1 commit into
mainfrom
fix/docs-pr-required-checks

Conversation

@lllamnyp

@lllamnyp Timofei Larkin (lllamnyp) commented Jun 12, 2026

Copy link
Copy Markdown
Member

What

Move the path filtering on the e2e and release-smoke workflows off the on: trigger and into a per-job if: gate, so that PRs which touch none of the filtered paths (docs-only PRs in particular) become mergeable.

Closes #333.

Why

Branch protection on main requires six contexts: DCO, verify, image-multiarch, kamaji-datastore, smoke (helm), smoke (manifest). The last three come from workflows that filtered themselves out at the trigger:

  • kamaji-datastore (e2e.yml) — pull_request: paths-ignore: ['**.md', 'docs/**']
  • smoke (helm) / smoke (manifest) (release-smoke.yml) — a pull_request: paths: allowlist

A workflow skipped at the trigger never creates its check context, so branch protection holds the required context in Expected forever and the PR stays BLOCKED. enforce_admins is on, so there is no admin-merge bypass. That is exactly what happened to #331.

How

The fix relies on the distinction GitHub draws between the two ways a check can be absent:

  • skipped at the trigger → context never reported → blocks (the bug);
  • skipped via a job-level if: → context reported as skipped → branch protection treats it as passing.

So each workflow now:

  1. always triggers on pull_request (no path filter);
  2. runs a cheap changes job (dorny/paths-filter, no checkout — it uses the PR API, ~seconds) that detects whether relevant paths changed;
  3. guards the expensive job with if: on that output, plus github.event_name != 'pull_request' so tag pushes / manual dispatch still run unconditionally.

On a docs-only PR the workflows start, the detector runs, and the ~30–45 min kamaji-datastore and the two kind smokes are skipped (zero compute) — but each required context reports skipped → passing. The cost-saving intent of the original filters is preserved; only the deadlock is removed.

The smoke matrix (mode: [manifest, helm]) is kept as-is so the required context names smoke (helm) / smoke (manifest) are unchanged — splitting it into two jobs would rename the contexts and require editing branch protection.

Verification needed before relying on it

Confirm that the smoke matrix job, when skipped via the job-level if:, still emits the per-leg contexts smoke (helm) and smoke (manifest) as skipped rather than a single collapsed smoke. It normally does, but this PR is itself a non-docs change (it edits workflows), so its own run will exercise the jobs rather than skip them — the skip path is best confirmed with a throwaway docs-only PR stacked on this branch, or by watching the next real docs-only PR. If the matrix collapses, the fallback is a sentinel job.

Note on #331

#331 (the docs PR that surfaced this) was unblocked out-of-band by dispatching both workflows against its head ref via workflow_dispatch, which produces the three contexts on the head SHA. That is a one-off; this PR removes the need to repeat it.

Summary by CodeRabbit

  • Chores
    • Updated CI/CD workflows to improve test execution efficiency. Documentation-only pull requests now display test status checks as skipped rather than omitted, while expensive integration tests are skipped when only non-code files are modified.

The `kamaji-datastore`, `smoke (helm)` and `smoke (manifest)` contexts are
required by branch protection, but their workflows filtered themselves out at
the `on:` trigger via path filters. A workflow skipped at the trigger never
reports its check, so the required contexts stayed in "Expected" and blocked
any PR that touched none of the filtered paths (docs-only PRs in particular).
enforce_admins is on, so there was no admin bypass either.

Move the path filtering off the trigger and into a cheap `changes` job
(dorny/paths-filter, no checkout): the workflows now always start, and the
expensive jobs are skipped via a job-level `if:` when nothing relevant
changed. A job skipped via `if:` still reports its check as "skipped", which
branch protection treats as passing — so docs-only PRs are mergeable while the
~30-45 min e2e and the kind smokes still skip (zero compute) on them. The
smoke matrix is kept intact so the required context names are unchanged.

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@gemini-code-assist

Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Both e2e and release-smoke workflows are refactored to move path filtering from trigger level to job-level conditionals. A new changes job using dorny/paths-filter detects relevant file modifications in seconds, while expensive jobs conditionally run only when needed. This ensures docs-only PRs emit all required status checks as skipped rather than never triggering.

Changes

Workflow Conditional Gating for Docs-Only PR Unblock

Layer / File(s) Summary
E2E workflow conditional gating
.github/workflows/e2e.yml
pull_request trigger is simplified to remove paths-ignore; a changes job detects non-doc code files via dorny/paths-filter, and kamaji-datastore job now runs conditionally only for tag/dispatch events or when changes.outputs.code == 'true'.
Release-smoke workflow conditional gating
.github/workflows/release-smoke.yml
pull_request trigger is simplified to remove paths allowlist; a changes job detects release-relevant files (workflows, charts, api, hack/release-smoke.sh, Docker/Makefile) via dorny/paths-filter, and smoke matrix now runs conditionally only for dispatch events or when changes.outputs.release == 'true'.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hops through workflows bright,
Where docs won't block the tests at night,
Cheap checks now gate the costly run,
Skipped checks report—the deadlock's done!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: moving path filters from workflow triggers to per-job gating to fix the docs-only PR branch protection deadlock.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from #333: removes path filters from workflow triggers, adds dorny/paths-filter change-detection jobs, guards expensive jobs with conditional logic, and preserves matrix job contexts for branch protection.
Out of Scope Changes check ✅ Passed All changes are scoped to the two GitHub Actions workflows (e2e.yml and release-smoke.yml) and directly address the requirements in #333 with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/docs-pr-required-checks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/e2e.yml:
- Line 35: The workflow uses an unpinned action reference
`dorny/paths-filter@v3`; replace that tag with a specific commit SHA for the
intended v3 release to mitigate supply-chain risk. Locate the `uses:
dorny/paths-filter@v3` entry in the e2e.yml workflow, find the corresponding v3
commit SHA from the dorny/paths-filter GitHub releases or tags, and update the
line to `uses: dorny/paths-filter@<commit-sha>` ensuring the SHA matches the
v3.x release you want to pin to.
- Around line 48-52: The dependent jobs 'kamaji-datastore' (in e2e.yml) and
'smoke' (in release-smoke.yml) currently declare needs: changes but their
existing if: condition is skipped when the 'changes' job itself is skipped;
update each job's if to guard with always(), e.g. if: ${{ always() &&
(github.event_name != 'pull_request' || needs.changes.outputs.code == 'true')
}}, so the job evaluates the condition even when 'changes' is skipped and will
run correctly for tag pushes and workflow_dispatch; keep the needs: changes
declaration and only change the if expression to include always().
- Around line 25-45: Add a minimal permissions block to the "changes" job so it
doesn't run with default repo permissions; under the job named changes (the job
that defines outputs from the dorny/paths-filter step with id filter) add e.g.
permissions: pull-requests: read (and any other specific read-only scopes you
need) to limit access to read-only PR metadata via the GitHub API.

In @.github/workflows/release-smoke.yml:
- Around line 27-51: In the "changes" job (the job block labeled changes that
runs the dorny/paths-filter step with id "filter"), replace the default
repository permissions by adding a minimal permissions block that grants only
what is needed to read PR metadata (e.g., set pull-requests: read and contents:
none) so the job has explicit read-only access and no write permissions; ensure
the permissions block is placed at the same indentation level as runs-on and
steps within the "changes" job.
- Line 35: The workflow uses an unpinned action reference
"dorny/paths-filter@v3" which must be replaced with a commit SHA to mitigate
supply-chain risks; update the "uses: dorny/paths-filter@v3" line to "uses:
dorny/paths-filter@<commit-sha>" where <commit-sha> is the full SHA of the v3
release commit (verify the SHA matches the intended v3.x tag on the
dorny/paths-filter repository before committing).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aea80e42-6775-4d26-9aba-4a54053ee804

📥 Commits

Reviewing files that changed from the base of the PR and between 7d20b1a and 9ea07e4.

📒 Files selected for processing (2)
  • .github/workflows/e2e.yml
  • .github/workflows/release-smoke.yml

Comment thread .github/workflows/e2e.yml
Comment on lines +25 to +45
changes:
# Cheap (~seconds, no checkout — dorny uses the PR API) gate that decides
# whether the expensive job below needs to run. PR-only: tag pushes and
# manual dispatch always run the suite unconditionally (see the `if` on
# kamaji-datastore).
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: dorny/paths-filter@v3
id: filter
with:
# `code` is true when the PR touches anything the e2e suite
# exercises. Only-negated patterns get an implicit `**`, so this
# reads as "all files except Markdown and docs/**" — i.e. docs-only
# PRs leave `code` false and the ~30-45 min run below is skipped.
filters: |
code:
- '!**/*.md'
- '!docs/**'

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Restrict permissions for the changes job.

The changes job uses default repository permissions. Since it only reads PR metadata via the GitHub API (no checkout, no writes), it should declare minimal read-only permissions.

🔐 Proposed permissions block
   changes:
     if: github.event_name == 'pull_request'
     runs-on: ubuntu-latest
+    permissions:
+      pull-requests: read
     outputs:
       code: ${{ steps.filter.outputs.code }}
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 25-45: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e.yml around lines 25 - 45, Add a minimal permissions
block to the "changes" job so it doesn't run with default repo permissions;
under the job named changes (the job that defines outputs from the
dorny/paths-filter step with id filter) add e.g. permissions: pull-requests:
read (and any other specific read-only scopes you need) to limit access to
read-only PR metadata via the GitHub API.

Source: Linters/SAST tools

Comment thread .github/workflows/e2e.yml
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: dorny/paths-filter@v3

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin dorny/paths-filter to a commit SHA.

The action reference dorny/paths-filter@v3 is unpinned. Per static analysis policy, action references should be pinned to a commit SHA to prevent supply-chain attacks.

🔒 Proposed fix
-      - uses: dorny/paths-filter@v3
+      - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36  # v3.0.2
         id: filter

Verify the SHA corresponds to the intended v3.x release in the dorny/paths-filter repository.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: dorny/paths-filter@v3
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
🧰 Tools
🪛 zizmor (1.25.2)

[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e.yml at line 35, The workflow uses an unpinned action
reference `dorny/paths-filter@v3`; replace that tag with a specific commit SHA
for the intended v3 release to mitigate supply-chain risk. Locate the `uses:
dorny/paths-filter@v3` entry in the e2e.yml workflow, find the corresponding v3
commit SHA from the dorny/paths-filter GitHub releases or tags, and update the
line to `uses: dorny/paths-filter@<commit-sha>` ensuring the SHA matches the
v3.x release you want to pin to.

Source: Linters/SAST tools

Comment thread .github/workflows/e2e.yml
Comment on lines +48 to +52
needs: changes
# Always run for tag pushes / manual dispatch; for PRs, run only when
# non-docs files changed. When skipped on a docs-only PR the check still
# reports (as "skipped" = passing), so the PR is not blocked.
if: ${{ github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' }}

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.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Shared root cause: needs: changes breaks non-PR execution in both workflows.

Both .github/workflows/e2e.yml (kamaji-datastore job) and .github/workflows/release-smoke.yml (smoke job) declare needs: changes, but the changes job only runs when github.event_name == 'pull_request'. In GitHub Actions, when a needed job is skipped, the dependent job is skipped by default—regardless of its if: condition. This means:

  • On tag push (e2e.yml trigger line 17), kamaji-datastore will never run
  • On workflow_dispatch (release-smoke.yml trigger line 20), smoke will never run

The shared fix is to wrap both jobs' conditions in always():

e2e.yml:

   kamaji-datastore:
     needs: changes
-    if: ${{ github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' }}
+    if: ${{ always() && (github.event_name != 'pull_request' || needs.changes.outputs.code == 'true') }}

release-smoke.yml:

   smoke:
     needs: changes
-    if: ${{ github.event_name != 'pull_request' || needs.changes.outputs.release == 'true' }}
+    if: ${{ always() && (github.event_name != 'pull_request' || needs.changes.outputs.release == 'true') }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
needs: changes
# Always run for tag pushes / manual dispatch; for PRs, run only when
# non-docs files changed. When skipped on a docs-only PR the check still
# reports (as "skipped" = passing), so the PR is not blocked.
if: ${{ github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' }}
needs: changes
# Always run for tag pushes / manual dispatch; for PRs, run only when
# non-docs files changed. When skipped on a docs-only PR the check still
# reports (as "skipped" = passing), so the PR is not blocked.
if: ${{ always() && (github.event_name != 'pull_request' || needs.changes.outputs.code == 'true') }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e.yml around lines 48 - 52, The dependent jobs
'kamaji-datastore' (in e2e.yml) and 'smoke' (in release-smoke.yml) currently
declare needs: changes but their existing if: condition is skipped when the
'changes' job itself is skipped; update each job's if to guard with always(),
e.g. if: ${{ always() && (github.event_name != 'pull_request' ||
needs.changes.outputs.code == 'true') }}, so the job evaluates the condition
even when 'changes' is skipped and will run correctly for tag pushes and
workflow_dispatch; keep the needs: changes declaration and only change the if
expression to include always().

Comment on lines +27 to +51
changes:
# Cheap (~seconds, no checkout) gate. PR-only: manual dispatch always runs
# the smoke matrix unconditionally (see the `if` on smoke).
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
outputs:
release: ${{ steps.filter.outputs.release }}
steps:
- uses: dorny/paths-filter@v3
id: filter
with:
# True when the PR touches the tag-release machinery or anything it
# ships. Matches the paths this workflow used to filter on at the
# trigger; PRs that touch none of these skip the two kind smokes.
filters: |
release:
- '.github/workflows/release-smoke.yml'
- '.github/workflows/docker-publish.yml'
- '.github/workflows/release-assets.yml'
- '.github/workflows/helm-publish.yml'
- 'hack/release-smoke.sh'
- 'charts/**'
- 'Makefile'
- 'Dockerfile'
- 'api/**'

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Restrict permissions for the changes job.

The changes job uses default repository permissions. Since it only reads PR metadata via the GitHub API (no checkout, no writes), it should declare minimal read-only permissions.

🔐 Proposed permissions block
   changes:
     if: github.event_name == 'pull_request'
     runs-on: ubuntu-latest
+    permissions:
+      pull-requests: read
     outputs:
       release: ${{ steps.filter.outputs.release }}
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 27-51: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-smoke.yml around lines 27 - 51, In the "changes"
job (the job block labeled changes that runs the dorny/paths-filter step with id
"filter"), replace the default repository permissions by adding a minimal
permissions block that grants only what is needed to read PR metadata (e.g., set
pull-requests: read and contents: none) so the job has explicit read-only access
and no write permissions; ensure the permissions block is placed at the same
indentation level as runs-on and steps within the "changes" job.

Source: Linters/SAST tools

outputs:
release: ${{ steps.filter.outputs.release }}
steps:
- uses: dorny/paths-filter@v3

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin dorny/paths-filter to a commit SHA.

The action reference dorny/paths-filter@v3 is unpinned. Per static analysis policy, action references should be pinned to a commit SHA to prevent supply-chain attacks.

🔒 Proposed fix
-      - uses: dorny/paths-filter@v3
+      - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36  # v3.0.2
         id: filter

Verify the SHA corresponds to the intended v3.x release in the dorny/paths-filter repository.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: dorny/paths-filter@v3
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
🧰 Tools
🪛 zizmor (1.25.2)

[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-smoke.yml at line 35, The workflow uses an
unpinned action reference "dorny/paths-filter@v3" which must be replaced with a
commit SHA to mitigate supply-chain risks; update the "uses:
dorny/paths-filter@v3" line to "uses: dorny/paths-filter@<commit-sha>" where
<commit-sha> is the full SHA of the v3 release commit (verify the SHA matches
the intended v3.x tag on the dorny/paths-filter repository before committing).

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docs-only PRs are unmergeable: required checks skipped at trigger never report

1 participant