[ci] Avoid running full CI mid-stack for GH-native stacks, same as we do for Graphite (#95218)

Running CI only on the top and bottom of the stack avoids expensive
`O(n^2)` behavior when merging one PR at a time and recursively rebasing
all the others starting at the bottom of the stack.

GitHub added this to the webhook/GH actions object for us to be able to
do this: https://vercel.slack.com/archives/C01SKER9S1G/p1777926655162839

Prior to this PR being part of a stack, fails open (runs full CI):
<img width="1938" height="993" alt="Screenshot 2026-06-26 at 3 03 37 PM"
src="https://github.com/user-attachments/assets/54d6055a-2860-45b3-a03a-a9356b3db2a4"
/>

Example mid-stack run (skips the full e2e jobs):
<img width="1917" height="664" alt="Screenshot 2026-06-26 at 3 24 33 PM"
src="https://github.com/user-attachments/assets/9ee466da-f3ea-4298-b5b8-2527d197c121"
/>

https://github.com/vercel/next.js/actions/runs/28268549361/job/83760907003?pr=95219
This commit is contained in:
Benjamin Woodruff
2026-07-01 23:22:24 -07:00
committed by GitHub
parent 41114a31b8
commit a26a1d640b
2 changed files with 33 additions and 9 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ env:
jobs:
optimize-ci:
uses: ./.github/workflows/graphite_ci_optimizer.yml
uses: ./.github/workflows/pr_stack_optimizer.yml
secrets: inherit
changes:
@@ -1,16 +1,19 @@
# Avoid running the full CI on mid-stack PRs: https://graphite.dev/docs/stacking-and-ci
#
# We still run some high-signal low-cost jobs (e.g. lint, unit tests) on these mid-stack PRs, just
# not anything slow (e.g. integration tests).
# This supports both Graphite stacks and GitHub's native stacked PRs. For either kind of stack, we
# only run the full CI on the very top and very bottom of the stack.
#
# We still run some high-signal low-cost jobs (e.g. lint, unit tests) on mid-stack PRs, just not
# anything slow (e.g. integration tests).
#
# Because we don't use Graphite's CI batching, full CI will still run on every PR individually
# before it merges. The goal is just to avoid wasting CI capacity when frequently rebasing large
# stacks.
#
# This can by bypassed by labeling a PR with 'CI Bypass Graphite Optimization', and manually
# re-running CI.
# This can by bypassed by labeling a PR with 'CI Bypass PR Stack Optimization' (or the legacy
# 'CI Bypass Graphite Optimization' label), and manually re-running CI.
name: Graphite CI Optimizer
name: PR Stack Optimizer
on:
workflow_call:
outputs:
@@ -29,16 +32,34 @@ env:
HAS_BYPASS_LABEL: |-
${{
github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'CI Bypass Graphite Optimization')
(
contains(github.event.pull_request.labels.*.name, 'CI Bypass PR Stack Optimization') ||
contains(github.event.pull_request.labels.*.name, 'CI Bypass Graphite Optimization')
)
}}
# GitHub's native stacked PRs expose the PR's position within the stack. We skip expensive CI on
# mid-stack PRs. Fail open if we don't have `stack.size`.
IS_MID_STACK_NATIVE: |-
${{
github.event_name == 'pull_request' &&
github.event.pull_request.stack.size != '' &&
github.event.pull_request.stack.position != 1 &&
github.event.pull_request.stack.position != github.event.pull_request.stack.size
}}
jobs:
optimize-ci:
name: Graphite CI Optimizer
name: PR Stack Optimizer
runs-on: ubuntu-latest
outputs:
# `== 'true'` comparison: If `step.check-skip` fails (`continue-on-error`), that output will
# be an empty string, which sets our `skip` output to `'false'`.
skip: ${{ env.HAS_BYPASS_LABEL == 'false' && steps.check-skip.outputs.skip == 'true' }}
#
# We skip if either Graphite's optimizer says to skip, or the PR is a mid-stack PR in a
# GitHub native stack. The bypass label overrides both.
skip: |-
${{ env.HAS_BYPASS_LABEL == 'false' && (
steps.check-skip.outputs.skip == 'true' || env.IS_MID_STACK_NATIVE == 'true'
) }}
steps:
- name: Optimize CI
id: check-skip
@@ -52,6 +73,9 @@ jobs:
echo 'github.event_name: ${{ github.event_name }}'
echo "secrets.GRAPHITE_CI_OPTIMIZER_TOKEN != '': ${GRAPHITE_CI_OPTIMIZER_TOKEN_NON_EMPTY}"
echo 'env.HAS_BYPASS_LABEL: ${{ env.HAS_BYPASS_LABEL }}'
echo 'env.IS_MID_STACK_NATIVE: ${{ env.IS_MID_STACK_NATIVE }}'
echo 'github.event.pull_request.stack.position: ${{ github.event.pull_request.stack.position }}'
echo 'github.event.pull_request.stack.size: ${{ github.event.pull_request.stack.size }}'
echo 'steps.check-skip.outputs.skip: ${{ steps.check-skip.outputs.skip }}'
env: