This is an automated email from the ASF dual-hosted git repository.
Yicong-Huang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new b8c1abba42 fix(ci): expand Auto Queue triggers and skip when queue
head is in flight (#4845)
b8c1abba42 is described below
commit b8c1abba427ce961d47715e5d50382db6f9bbfa3
Author: Yicong Huang <[email protected]>
AuthorDate: Sat May 2 23:07:56 2026 -0700
fix(ci): expand Auto Queue triggers and skip when queue head is in flight
(#4845)
### What changes were proposed in this PR?
Two changes to `.github/workflows/auto-queue.yml`.
**1. More triggers, shorter cron.** Add `pull_request
{auto_merge_enabled, ready_for_review}`, `pull_request_review
{submitted}`, `workflow_run {Required Checks completed}`, and drop cron
from hourly to every 5 min. Each event covers a state change that
previously waited up to an hour for cron — most notably `workflow_run
completed` lets us bump the next PR the moment the head PR's CI finishes
(pass or fail).
**2. In-flight guard.** GraphQL now pulls `statusCheckRollup.state`. If
any eligible PR has `mergeStateStatus != BEHIND` and CI is `PENDING /
EXPECTED / SUCCESS`, the run exits without bumping anyone — the queue
head is already moving, and preempting CI on another PR would just force
a re-bump after the head merges. `BEHIND + PENDING` does not count (CI
runs on pre-update code), and `BLOCKED + FAILURE/ERROR` does not count
(won't auto-merge, release the guard).
### Any related issues, documentation, discussions?
Tracking issue: #4553. Builds on #4672 (initial workflow) and #4678
(UNKNOWN retry + eligibility gates).
### How was this PR tested?
Workflow YAML parses clean. Decision logic is exercised by every trigger
after merge — no separate test harness for this workflow today.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.7, 1M context)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
.github/workflows/auto-queue.yml | 112 ++++++++++++++++++++++++++++++++++++---
1 file changed, 104 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/auto-queue.yml b/.github/workflows/auto-queue.yml
index 6bdbde8cec..6045030d7c 100644
--- a/.github/workflows/auto-queue.yml
+++ b/.github/workflows/auto-queue.yml
@@ -18,9 +18,17 @@
#
# Triggers:
# * push to main: advance the queue right after a merge.
-# * hourly cron: catch PRs that became BEHIND while no merge happened
-# (e.g. a force-push to base, or a PR enabling auto-merge after the
-# last main push).
+# * pull_request {auto_merge_enabled, ready_for_review}: a PR just
+# became eligible — kick the queue without waiting for cron.
+# * pull_request_review {submitted}: an approval may have just made
+# a PR eligible (script filters non-approval review states).
+# * workflow_run {Required Checks, completed}: the head PR's CI
+# just finished. On success, auto-merge fires and the next push to
+# main triggers us; on failure, the head PR's CI moves from PENDING
+# to FAILURE so the in-flight guard releases — this trigger gives
+# us a same-second kick instead of waiting on cron.
+# * 5-minute cron: bounded safety net for any missed event delivery
+# and for PRs that became BEHIND without producing any of the above.
# * workflow_dispatch: manual smoke test.
#
# Strategy: scan open PRs targeting main and pick the oldest eligible PR with
@@ -29,6 +37,15 @@
# not conflicting, reviewDecision=APPROVED, and zero unresolved review threads.
# This avoids burning CI on PRs blocked on review.
#
+# In-flight guard: if any eligible PR is already past the BEHIND state and
+# its required CI is still running (mergeStateStatus != BEHIND and
+# statusCheckRollup state is PENDING/EXPECTED), the run exits without
+# bumping anyone else. That PR is the queue head; bumping a different PR
+# while it is in flight would just preempt CI capacity for a PR that
+# would still need re-bumping after the head merges. PRs that are
+# BEHIND with PENDING checks do NOT count as in-flight — that CI is on
+# pre-update code and would need to re-run after updateBranch anyway.
+#
# mergeStateStatus is computed asynchronously and is UNKNOWN for a window
# after a base-branch push. If at least one eligible PR is UNKNOWN, retry
# with backoff up to ~2min to let it settle. If everything is settled and
@@ -43,8 +60,15 @@ name: Auto Queue
on:
push:
branches: [main]
+ pull_request:
+ types: [auto_merge_enabled, ready_for_review]
+ pull_request_review:
+ types: [submitted]
+ workflow_run:
+ workflows: [Required Checks]
+ types: [completed]
schedule:
- - cron: '0 * * * *'
+ - cron: '*/5 * * * *'
workflow_dispatch:
permissions:
@@ -65,6 +89,22 @@ jobs:
script: |
const { owner, repo } = context.repo;
+ // pull_request_review fires for any submitted review (Comment /
+ // Approve / Request changes). Only Approve can newly satisfy the
+ // reviewDecision=APPROVED gate, so other states are pure no-ops
+ // worth short-circuiting before the GraphQL call.
+ if (
+ context.eventName === 'pull_request_review' &&
+ context.payload.review?.state !== 'approved'
+ ) {
+ core.info(
+ `Skip: pull_request_review state=` +
+ `${context.payload.review?.state} (only "approved" can ` +
+ `change queue eligibility).`
+ );
+ return;
+ }
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// 0, 10, 20, 30, 30, 30 = 120s total wall-clock budget across
// attempts. Short ramp catches the common case where
@@ -92,6 +132,13 @@ jobs:
reviewThreads(first: 100) {
nodes { isResolved }
}
+ commits(last: 1) {
+ nodes {
+ commit {
+ statusCheckRollup { state }
+ }
+ }
+ }
}
}
}
@@ -125,25 +172,74 @@ jobs:
}
core.startGroup(`Attempt ${attempt + 1}/${BACKOFFS_MS.length}`);
- const data = await github.graphql(query, { owner, name: repo });
+ let data;
+ try {
+ data = await github.graphql(query, { owner, name: repo });
+ } catch (e) {
+ // Transient GitHub API failures (5xx, "terminated", etc.)
+ // shouldn't kill the whole run — the backoff loop is exactly
+ // the right place to absorb them. Try again next attempt.
+ core.warning(
+ `GraphQL query failed (status ${e.status ?? '?'}): ` +
+ `${e.message}. Retrying after backoff.`
+ );
+ core.endGroup();
+ continue;
+ }
const all = data.repository.pullRequests.nodes;
core.info(`Scanned ${all.length} open PR(s) targeting main.`);
const behind = [];
const unknown = [];
+ const inFlight = [];
for (const p of all) {
const verdict = classify(p);
core.info(` #${p.number} ${verdict} — ${p.title}`);
if (!verdict.startsWith('eligible')) continue;
- if (p.mergeStateStatus === 'BEHIND') behind.push(p);
- else if (p.mergeStateStatus === 'UNKNOWN') unknown.push(p);
+ if (p.mergeStateStatus === 'BEHIND') {
+ behind.push(p);
+ continue;
+ }
+ if (p.mergeStateStatus === 'UNKNOWN') {
+ unknown.push(p);
+ continue;
+ }
+ // Eligible AND not BEHIND/UNKNOWN: this PR is ahead of any
+ // BEHIND PR in the queue. Treat it as in-flight only if its
+ // current required CI is actually working toward a merge.
+ // PENDING/EXPECTED (CI still running on the with-main code)
+ // means "wait for it"; SUCCESS (about to auto-merge) means
+ // "wait for it"; FAILURE/ERROR (CI failed) is NOT in-flight
+ // — auto-merge will not fire, queue can advance past it.
+ const ciState =
+ p.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state;
+ if (
+ ciState === 'PENDING' ||
+ ciState === 'EXPECTED' ||
+ ciState === 'SUCCESS'
+ ) {
+ inFlight.push({ pr: p, ciState });
+ }
}
core.info(
`Eligible: ${behind.length} BEHIND, ${unknown.length} UNKNOWN,
` +
- `rest already up-to-date or otherwise blocked.`
+ `${inFlight.length} in-flight (queue head still merging), ` +
+ `rest blocked on failed CI or non-CI gates.`
);
+ if (inFlight.length > 0) {
+ const head = inFlight[0];
+ core.info(
+ `Skip: PR #${head.pr.number} is in flight ` +
+ `(state=${head.pr.mergeStateStatus}, ci=${head.ciState}). ` +
+ `Letting it finish to avoid preempting CI on a PR we may ` +
+ `need to re-bump.`
+ );
+ core.endGroup();
+ return;
+ }
+
if (behind.length > 0) {
let updated = null;
for (const pr of behind) {