Summary
Docs-only PRs (and any PR that doesn't touch code/charts/workflows) are unmergeable because three required status checks never report.
Root cause
Branch protection on main requires six contexts:
DCO verify image-multiarch kamaji-datastore smoke (helm) smoke (manifest)
Three of them are produced by workflows that are filtered out at the trigger level:
kamaji-datastore — .github/workflows/e2e.yml has pull_request: paths-ignore: ['**.md', 'docs/**'], so the workflow never starts on a docs-only change.
smoke (helm) / smoke (manifest) — .github/workflows/release-smoke.yml has a pull_request: paths: allowlist (workflows, charts/**, api/**, Makefile, Dockerfile, hack/release-smoke.sh), so it never starts otherwise.
When a workflow is skipped at the on: trigger via a path filter, GitHub never creates the check context. Branch protection then holds the required context in the Expected state indefinitely, and the PR stays BLOCKED. enforce_admins is enabled, so there is no admin-merge bypass either.
This is the canonical GitHub "skipped but required" deadlock. See: https://docs.github.com/actions/how-tos/manage-workflow-runs/skip-workflow-runs and GitHub's guidance on handling skipped-but-required checks.
Why the path filters exist
The filters are deliberate and worth keeping: the kamaji-datastore e2e run provisions kind + cert-manager + Kamaji and costs ~30–45 minutes; the smoke matrix spins up kind twice. We don't want to pay that on a typo fix to a .md file. The bug is where the filtering happens, not that it happens.
Fix
Move the path filtering off the on: trigger and into the jobs:
- Drop
paths-ignore / paths from the pull_request: triggers in e2e.yml and release-smoke.yml.
- Add a cheap
changes job (e.g. dorny/paths-filter) that detects whether relevant paths changed — runs in seconds, no Go/Docker setup.
- Guard the expensive jobs with
if: needs.changes.outputs.<flag> == 'true'.
The key distinction: a job skipped via a job-level if: still reports its check context with conclusion skipped, and branch protection treats a skipped required check as passing. So on a docs-only PR the workflows start, the cheap detector runs, the expensive jobs are skipped (zero compute cost), and all three contexts report skipped → pass. The deadlock is gone and the cost-saving intent is preserved.
Caveat to verify
Confirm that the smoke matrix job, when skipped via a job-level if:, still emits the per-leg contexts smoke (helm) and smoke (manifest) as skipped (rather than a single collapsed smoke). If it collapses, split the matrix into two named jobs or add a sentinel job.
Immediate unblock for the PR that surfaced this
#331 was unblocked by manually dispatching both workflows (workflow_dispatch) against the PR's head ref, which produces the three contexts on the head SHA. That runs the real suites and must be repeated per docs PR — the fix above removes the need for that ritual.
Summary
Docs-only PRs (and any PR that doesn't touch code/charts/workflows) are unmergeable because three required status checks never report.
Root cause
Branch protection on
mainrequires six contexts:Three of them are produced by workflows that are filtered out at the trigger level:
kamaji-datastore—.github/workflows/e2e.ymlhaspull_request: paths-ignore: ['**.md', 'docs/**'], so the workflow never starts on a docs-only change.smoke (helm)/smoke (manifest)—.github/workflows/release-smoke.ymlhas apull_request: paths:allowlist (workflows,charts/**,api/**,Makefile,Dockerfile,hack/release-smoke.sh), so it never starts otherwise.When a workflow is skipped at the
on:trigger via a path filter, GitHub never creates the check context. Branch protection then holds the required context in theExpectedstate indefinitely, and the PR staysBLOCKED.enforce_adminsis enabled, so there is no admin-merge bypass either.This is the canonical GitHub "skipped but required" deadlock. See: https://docs.github.com/actions/how-tos/manage-workflow-runs/skip-workflow-runs and GitHub's guidance on handling skipped-but-required checks.
Why the path filters exist
The filters are deliberate and worth keeping: the
kamaji-datastoree2e run provisions kind + cert-manager + Kamaji and costs ~30–45 minutes; the smoke matrix spins up kind twice. We don't want to pay that on a typo fix to a.mdfile. The bug is where the filtering happens, not that it happens.Fix
Move the path filtering off the
on:trigger and into the jobs:paths-ignore/pathsfrom thepull_request:triggers ine2e.ymlandrelease-smoke.yml.changesjob (e.g.dorny/paths-filter) that detects whether relevant paths changed — runs in seconds, no Go/Docker setup.if: needs.changes.outputs.<flag> == 'true'.The key distinction: a job skipped via a job-level
if:still reports its check context with conclusionskipped, and branch protection treats a skipped required check as passing. So on a docs-only PR the workflows start, the cheap detector runs, the expensive jobs are skipped (zero compute cost), and all three contexts report skipped → pass. The deadlock is gone and the cost-saving intent is preserved.Caveat to verify
Confirm that the
smokematrix job, when skipped via a job-levelif:, still emits the per-leg contextssmoke (helm)andsmoke (manifest)as skipped (rather than a single collapsedsmoke). If it collapses, split the matrix into two named jobs or add a sentinel job.Immediate unblock for the PR that surfaced this
#331 was unblocked by manually dispatching both workflows (
workflow_dispatch) against the PR's head ref, which produces the three contexts on the head SHA. That runs the real suites and must be repeated per docs PR — the fix above removes the need for that ritual.