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) {

Reply via email to