Summary
Two CI workflows are configured as required status checks in branch protection (org ruleset), but both filter at the workflow trigger level via on.pull_request.paths:
- Schema Parity Gate —
.github/workflows/schema-parity.yml (context: schema-parity)
- Container Security —
.github/workflows/container-security.yml (contexts: verify-non-root, verify-prod-frontend-uid)
When a PR changes no files matching those path globs, the workflow never runs, so the required check context is never reported. GitHub then keeps the PR in mergeStateStatus: BLOCKED — "Expected — Waiting for status to be reported" — even though the PR is approved and every check that did run is green. The result: every PR that doesn’t touch db/** (schema-parity) or docker/**, docker-compose*.yml, scripts/deploy/start.sh, src/mcp-server/Dockerfile (container-security) is silently un-mergeable.
Impact
Root cause
on.pull_request.paths filters at the workflow level, not the job level:
- Workflow not triggered (no path match) → GitHub creates no check run for that context → a required check by that name stays pending forever → merge blocked.
- This differs from a skipped job: if the workflow runs but a job is gated off via
if:, GitHub reports that job as skipped, which branch protection treats as neutral/success. The trap is assuming “skipped = pass” — that only holds when the workflow itself triggers.
The schema-parity.yml header already warns about exactly this:
"MAINTAINER NOTE: do NOT add this job to required-status-checks in branch protection until it has been green for ~1 week… any path-filter false-negative would brick PRs that don’t touch DB files. The workflow self-skips on unrelated PRs… so leaving it optional is safe."
So schema-parity should not be a required check while it is path-filtered; verify-non-root / verify-prod-frontend-uid have the same property.
Recommended fix (A) — un-require the path-filtered contexts
In the org ruleset for abilityai/trinity (the requirement is not classic branch protection — branches/dev/protection 404s — nor a repo ruleset — repos/abilityai/trinity/rulesets is empty — so it lives at orgs/abilityai/rulesets), remove these from Require status checks to pass:
schema-parity
verify-non-root
verify-prod-frontend-uid
They keep running (and gating) on PRs that touch their paths; they just stop blocking unrelated PRs. Requires repo/org admin (a maintain collaborator cannot edit the ruleset).
Alternative fix (B) — keep them required, make the context always report
If enforcement-on-every-PR is desired, convert each to the skip-aware gate pattern instead of un-requiring:
- Drop
on.pull_request.paths so the workflow always triggers.
- Do the path discrimination inside (e.g.
dorny/paths-filter, or per-job if:).
- Add a final always-running gate job that carries the required context name and reports success when the heavy job was not applicable.
Note: naively just deleting on.paths would make container-security boot a full docker compose stack on every PR (slow/expensive) — the gate pattern avoids that. This is a code change and can land via PR; fix A cannot (it is a Settings/ruleset toggle, not a repo file).
Acceptance criteria
- A PR that touches neither
db/** nor the container paths can reach a mergeable state (no pending phantom required check).
- schema-parity / container-security still run and gate PRs that do touch their paths.
Evidence / references
Summary
Two CI workflows are configured as required status checks in branch protection (org ruleset), but both filter at the workflow trigger level via
on.pull_request.paths:.github/workflows/schema-parity.yml(context:schema-parity).github/workflows/container-security.yml(contexts:verify-non-root,verify-prod-frontend-uid)When a PR changes no files matching those path globs, the workflow never runs, so the required check context is never reported. GitHub then keeps the PR in
mergeStateStatus: BLOCKED— "Expected — Waiting for status to be reported" — even though the PR is approved and every check that did run is green. The result: every PR that doesn’t touchdb/**(schema-parity) ordocker/**,docker-compose*.yml,scripts/deploy/start.sh,src/mcp-server/Dockerfile(container-security) is silently un-mergeable.Impact
services/+routers/) both show MERGEABLE + APPROVED + all visible checks green, butBLOCKED.Root cause
on.pull_request.pathsfilters at the workflow level, not the job level:if:, GitHub reports that job asskipped, which branch protection treats as neutral/success. The trap is assuming “skipped = pass” — that only holds when the workflow itself triggers.The
schema-parity.ymlheader already warns about exactly this:So
schema-parityshould not be a required check while it is path-filtered;verify-non-root/verify-prod-frontend-uidhave the same property.Recommended fix (A) — un-require the path-filtered contexts
In the org ruleset for
abilityai/trinity(the requirement is not classic branch protection —branches/dev/protection404s — nor a repo ruleset —repos/abilityai/trinity/rulesetsis empty — so it lives atorgs/abilityai/rulesets), remove these from Require status checks to pass:schema-parityverify-non-rootverify-prod-frontend-uidThey keep running (and gating) on PRs that touch their paths; they just stop blocking unrelated PRs. Requires repo/org admin (a
maintaincollaborator cannot edit the ruleset).Alternative fix (B) — keep them required, make the context always report
If enforcement-on-every-PR is desired, convert each to the skip-aware gate pattern instead of un-requiring:
on.pull_request.pathsso the workflow always triggers.dorny/paths-filter, or per-jobif:).Note: naively just deleting
on.pathswould makecontainer-securityboot a fulldocker composestack on every PR (slow/expensive) — the gate pattern avoids that. This is a code change and can land via PR; fix A cannot (it is a Settings/ruleset toggle, not a repo file).Acceptance criteria
db/**nor the container paths can reach a mergeable state (no pending phantom required check).Evidence / references
.github/workflows/schema-parity.yml(on.pull_request.paths, maintainer note in header).github/workflows/container-security.yml(on.pull_request.paths)