ci: run CI on stacked PRs, gated on real stack membership - #339
Merged
Merged
Conversation
sgathrid
force-pushed
the
ci/stacked-pr-ci
branch
from
August 11, 2026 15:15
bb2c9a1 to
b88e4b6
Compare
jeremy-wayland
approved these changes
Aug 12, 2026
jeremy-wayland
force-pushed
the
ci/stacked-pr-ci
branch
from
August 12, 2026 15:15
174e572 to
4eca375
Compare
`on.pull_request.branches` filters on a PR's *base*, and a stacked PR targets its parent topic branch. The allowlist `[main, worktree-rust-migration-v0.4.0, 'release/**']` therefore matched no stacked PR at all: #337 (base `chore/rmcp-3x-264`) drew zero Actions runs, so a stacked change was reviewed and merged with no verification behind it. `ci.yml` has no `workflow_dispatch`, so it could not even be triggered by hand. Widening the allowlist to topic-branch globs would admit every PR aimed at anyone's branch, which is not the intent. Instead the trigger is unfiltered and the policy moves into a `gate` job backed by `scripts/ci_gate.py`: * `push` — already filtered by the trigger. Run. * `pull_request` into a `TRUNK_PATTERNS` branch. Run, decided before any API call, so the path to `main` gains no new dependency. * `pull_request` belonging to a GitHub stack, whatever it targets. Run. * Any other `pull_request` — a one-off aimed at a topic branch. Skip. Stack membership is real data, not inferred from branch names: the `PullRequestStack` GraphQL API exposes `pullRequest.stack { number size }`, which returns a stack for #337 and #324 (stack #338, positions 2 and 1) and `null` for non-stacked PRs such as #320 and #318. `--selftest` covers all twelve decision-table cases and runs as the gate's first step, including the `push` path that could otherwise break `main` and a check that `release/*` does not match across a slash. Actions globs are not `fnmatch` — `*` stops at `/` while `**` crosses it — so the matcher is implemented rather than delegated. Unverifiable locally, and deliberately loud about it: whether an Actions installation token can read `PullRequestStack` is a different question from whether the logic is right. The local checks used a personal token. So the gate prints the raw GraphQL response including any `errors` array, never fails its own step, and fails *open* — an unreadable response runs CI with a `::warning::` saying the stacked-PR-only policy is not in effect, rather than silently skipping verification. This PR's own run is the test of that access. No CHANGELOG entry: CI plumbing, per `.agents/AGENTS.md:90`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment described `TRUNK_PATTERNS` as mirroring the old `on.pull_request.branches` allowlist "plus `on.push.branches`", but the two differ: the push filter has no `release/**`. Reading it as a union would suggest a push to `release/*` consults the list, when the trigger never fires for one at all — pushes are filtered before the gate runs, and `decide()` admits any non-PR event outright. Also states plainly that nothing enforces the correspondence with the YAML, so the sync is by hand. Comments only; no behavior change. `--selftest` 12/12, `actionlint` clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jeremy-wayland
force-pushed
the
ci/stacked-pr-ci
branch
from
August 12, 2026 15:22
4eca375 to
c43ffb1
Compare
krv-labs-ai-developer
self-requested a review
August 14, 2026 21:51
krv-labs-ai-developer
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
Stacked PRs target a topic branch, not
main. The oldpull_request.branchesallowlist therefore skipped CI entirely for stacks (see #337: zero runs until it was linked / this gate landed).This PR:
pull_requesttrigger (no base-branch filter).gatejob +scripts/ci_gate.pythat re-applies policy:main/ migration /release/**) → runci/stacked-pr-ci(b88e4b6)fix/mcp-protocol-negotiation(#337)Why this exists
mainpullRequest.stackis setNote: GitHub may already match some stack workflows against the stack trunk for
branches:filters. This PR does not rely on that: policy is explicit in-repo. The important fix is “linked stacked PR gets CI.”How the gate works
.github/workflows/ci.ymlpull_request:;gate→ conditional jobsscripts/ci_gate.py--selftest(12 cases)TRUNK_PATTERNSpull-requests: readenough for stack field (verified on a real run)Non-goals
main/ migration only).release.yml/docs.ymlfiring on stack trunk (pre-existing once stacks match trunk) — separate ops call if codesign cost is a concern.How to review
ci.yml— trigger comment +gatejob wiring (needs.gate.outputs.run).ci_gate.py—decide()+ selftest table; fail-open path.CI gate: run=truewith real GraphQL data (not only the fail-open branch).Validate
python3 scripts/ci_gate.py --selftest # On a stacked PR Actions run: gate job green; rust/composable run when run=trueMerge
Safe to close and rely on native stack-trunk matching if you want less CI surface; documented tradeoff, not a default.
Known flake (unrelated)
ensure_graph_succeeds_directly_when_update_produces_a_graphcan hitText file busy (os error 26)on Linux CI. Pre-existing onmain; this PR does not touch graphify. Rerun failed jobs if it trips.