Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/pr-stack-ci-gate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
52 changes: 52 additions & 0 deletions .github/actions/pr-stack-ci-gate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# PR Stack CI Gate

The `build_and_test` workflow calls this read-only action for same-repository PRs.
It infers branch-based stacks from open PR head/base branch names; no GitHub
stack API or Graphite integration is required. Fork PRs bypass the checkout and
this action entirely and run full CI immediately.

The first three PRs and every leaf run immediately. An internal PR polls the
closest three predecessors every five minutes: **any** current-head/current-base
`thank you, next` success releases full CI, while three terminal failures fail
the required gate. Unresolved waits open after five hours (six-hour job timeout).
Transient GitHub 5xx/429 errors retry every five minutes; other errors start
full CI so they cannot accidentally mark the aggregate green.

## Development

```bash
pnpm --dir .github/actions/pr-stack-ci-gate install --frozen-lockfile --ignore-scripts
pnpm --dir .github/actions/pr-stack-ci-gate types
pnpm --dir .github/actions/pr-stack-ci-gate build
pnpm --dir .github/actions/pr-stack-ci-gate test
```

Commit changes to `src/` **and** the generated `dist/index.js` and
`dist/licenses.txt`. Tests use an isolated Jest config because the repository's
main Jest config searches only the `test/` and package trees. A captured shape
from #99095's actual required-check response lives in `fixtures/`. The workflow
sparse-checks out only `action.yml` and `dist/index.js` from `${{ github.sha }}`;
that is the test-merge commit on PR runs. The caller and action job grant only
`checks: read`, `contents: read` and `pull-requests: read`. No secrets are
inherited, and `persist-credentials` is false.

## API budget and correctness

A stable middle-PR poll rechecks its own PR, finds successors, walks three
predecessor links and checks all three required results: **8 REST calls** versus
11 before this action. The `pulls.list` response already contains head/base
SHAs, so the three extra predecessor `pulls.get` calls are unnecessary while
waiting. Before opening or failing, the action re-reads current PR metadata and
the decisive predecessor(s) and check(s). We cannot stop at the first pending
predecessor: an older PR can already have passed; nor can we permanently cache
a terminal failure because that PR can succeed on rerun. Rechecking live branch
topology on *every* five-minute poll catches retargeted, closed, or leaf PRs at
the original cadence.

GitHub's `GITHUB_TOKEN` primary budget is typically **1,000/hour/repository**
for both REST requests and GraphQL points. Most REST GETs cost one point.
GraphQL may batch data into a one-point query, but the check-run-to-PR base-SHA
association and nested pagination would need independent correctness testing;
this implementation instead takes a measured REST reduction without weakening
the already verified current-base guard. An `ubuntu-slim` runner is unsuitable:
its 15-minute job limit is shorter than the gate's multi-hour wait.
5 changes: 5 additions & 0 deletions .github/actions/pr-stack-ci-gate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: 'PR Stack CI Gate'
description: 'Delay expensive CI for branch-based stacked pull requests until prior CI succeeds.'
runs:
using: 'node24'
main: 'dist/index.js'
3 changes: 3 additions & 0 deletions .github/actions/pr-stack-ci-gate/dist/index.js

Large diffs are not rendered by default.

590 changes: 590 additions & 0 deletions .github/actions/pr-stack-ci-gate/dist/licenses.txt

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions .github/actions/pr-stack-ci-gate/fixtures/successful-check.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"id": 107285638067,
"name": "thank you, next",
"status": "completed",
"conclusion": "success",
"head_sha": "360b493601939d820d35a77c2d4ec691ce12089d",
"html_url": "https://github.com/vercel/next.js/actions/runs/35887957948/job/107285638067",
"app": { "slug": "github-actions" },
"pull_requests": [
{
"number": 99095,
"head": { "sha": "360b493601939d820d35a77c2d4ec691ce12089d" },
"base": {
"ref": "ci/branch-stack-polling-gate",
"sha": "939d61265dcb4a6f91485eb08b0907062f71ab51"
}
}
]
}
Loading
Loading