ci: Auto Merge Queuer — re-add PRs dequeued by failed checks (up to 2x) (#13891)

## What

Adds `.github/workflows/merge-queue-auto-requeue.yml` — **Auto Merge
Queuer**.

When a PR is kicked **out of the merge queue because its required checks
failed** (`reason == CI_FAILURE`) or **timed out** (`reason ==
CI_TIMEOUT`), the workflow adds it straight back to the queue — **up to
twice**.

## Behavior

- **Trigger:** `pull_request: [dequeued]`, gated at the job `if` to
`CI_FAILURE` / `CI_TIMEOUT` only.
- **Manual removals** (`reason == MANUAL`) and every other reason
(`MERGE_CONFLICT`, `QUEUE_CLEARED`, `BRANCH_PROTECTIONS`, …) are
**ignored** — the job never runs for them.
- **Limit:** attempt count tracked via labels `auto-requeue-1` /
`auto-requeue-2` (max 2 re-adds).
- **Re-queue:** GraphQL `enqueuePullRequest` mutation, authenticated
with `secrets.ORG_ADMIN_TOKEN` (a `GITHUB_TOKEN` enqueue would not
re-trigger the `merge_group` check workflows).
- **One sticky comment, edited in place** (found by a hidden marker):
  - 1st kick → "added back (attempt 1 of 2)"
- 2nd kick → same comment edited → "re-added a second time (2 of 2) —
last automatic retry"
- 3rd kick → same comment edited → "🛑 no-go for auto re-add" and it does
**not** re-queue.

All `gh` calls run under the PAT, so the comment, labels and enqueue are
authored by the token owner (not `github-actions[bot]`).

## Notes for reviewers

- Requires repo secret `ORG_ADMIN_TOKEN` with merge-queue write (already
used by 26 workflows in this repo).
- `github.event.reason` on the `dequeued` payload drives the gate; worth
confirming on the first real CI-caused dequeue. Safe failure mode if
absent: the job no-ops.
This commit is contained in:
Shrinath Rao
2026-08-19 12:29:46 +00:00
committed by GitHub
parent 503a7054fb
commit e2b35f43f5
@@ -0,0 +1,129 @@
name: Auto Merge Queuer
# When a PR is kicked OUT of the merge queue because its required checks failed
# (reason CI_FAILURE) or timed out (reason CI_TIMEOUT), add it straight back to
# the queue — up to twice. Manual removals (reason MANUAL) and every other reason
# (MERGE_CONFLICT, QUEUE_CLEARED, BRANCH_PROTECTIONS, …) are ignored: the job `if`
# below never runs for them.
#
# Attempt count lives in the labels auto-requeue-1 / auto-requeue-2. A single
# sticky comment (marked with the hidden MARKER) is EDITED in place on each event:
# attempt 1 -> "re-added (1 of 2)"
# attempt 2 -> same comment edited to "re-added a second time (2 of 2)"
# 3rd kick -> same comment edited to "no-go for auto re-add" (no re-queue)
#
# Re-queueing uses the PAT (secrets.ORG_ADMIN_TOKEN) on purpose: an enqueue made
# with the default GITHUB_TOKEN would not re-trigger the merge_group check
# workflows. This is the repo's standard human-PAT secret, so the sticky comment,
# labels and enqueue are all authored as the human (not github-actions[bot]).
#
# REQUIRED — secrets.ORG_ADMIN_TOKEN (fine-grained PAT) permissions:
# openobserve: Pull requests: R/W (enqueuePullRequest + sticky comment)
# Issues: R/W (label create + add)
# NOTE: the `permissions:` block below constrains ONLY the run's GITHUB_TOKEN — it
# does NOT scope the PAT. Prefer a fine-grained PAT limited to this repo so its
# reach matches the two write scopes above rather than full org-admin.
on:
pull_request:
types: [dequeued]
# Applies to the run's GITHUB_TOKEN only (unused for API calls here — those use the
# PAT); kept minimal so nothing this job doesn't need is granted to GITHUB_TOKEN.
permissions:
contents: read
concurrency:
group: auto-requeue-${{ github.event.pull_request.number }}
cancel-in-progress: false
jobs:
requeue:
# Only dequeues caused by failed/timed-out required checks.
if: github.event.reason == 'CI_FAILURE' || github.event.reason == 'CI_TIMEOUT'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.ORG_ADMIN_TOKEN }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_NODE_ID: ${{ github.event.pull_request.node_id }}
REASON: ${{ github.event.reason }}
MAX_ATTEMPTS: "2"
MARKER: "<!-- auto-requeue-bot -->"
steps:
- name: Re-add to merge queue (up to twice) or mark no-go
run: |
set -euo pipefail
echo "PR #${PR_NUMBER} dequeued with reason: ${REASON}"
# --- current attempt count from labels -----------------------------
labels="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '.labels[].name')"
n=0
for i in $(seq 1 "$MAX_ATTEMPTS"); do
if grep -qx "auto-requeue-$i" <<<"$labels"; then n=$i; fi
done
echo "Prior auto-requeue attempts: $n"
# --- locate the sticky comment (if any) ----------------------------
comment_id="$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" --paginate \
--jq "map(select(.body // \"\" | contains(\"$MARKER\"))) | last | .id // empty")"
upsert_comment() {
# $1 = full comment body
local body="$1"
if [ -n "$comment_id" ]; then
gh api -X PATCH "repos/$REPO/issues/comments/$comment_id" -f body="$body" >/dev/null
echo "Edited sticky comment $comment_id"
else
comment_id="$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" -f body="$body" --jq '.id')"
echo "Created sticky comment $comment_id"
fi
}
# --- already used both retries -> give up --------------------------
if [ "$n" -ge "$MAX_ATTEMPTS" ]; then
echo "Attempts exhausted ($n/$MAX_ATTEMPTS); not re-queueing."
upsert_comment "$MARKER
🛑 **Auto-requeue exhausted** — this PR was dequeued again (reason \`${REASON}\`) after **${MAX_ATTEMPTS} automatic re-adds**.
This is a **no-go for auto re-add**. Please investigate the failing required checks and re-queue manually once they're green."
exit 0
fi
# --- attempt n+1: enqueue via PAT ----------------------------------
attempt=$((n + 1))
echo "Re-adding to merge queue (attempt ${attempt}/${MAX_ATTEMPTS})"
if ! gh api graphql -f query='
mutation($id: ID!) {
enqueuePullRequest(input: { pullRequestId: $id }) {
mergeQueueEntry { state position }
}
}' -F id="$PR_NODE_ID" > enqueue.json; then
echo "::error::enqueuePullRequest failed"
cat enqueue.json || true
upsert_comment "$MARKER
⚠️ **Auto-requeue failed** — tried to re-add this PR to the merge queue after a \`${REASON}\` dequeue but the enqueue API call failed. It has **not** been re-queued; please re-queue manually."
exit 1
fi
cat enqueue.json
# record the attempt so the next dequeue counts correctly
gh label create "auto-requeue-${attempt}" --repo "$REPO" \
--color BFD4F2 --description "Auto re-added to the merge queue (attempt ${attempt})" \
--force >/dev/null 2>&1 || true
gh pr edit "$PR_NUMBER" --repo "$REPO" --add-label "auto-requeue-${attempt}"
# --- sticky comment for this attempt -------------------------------
if [ "$attempt" -eq 1 ]; then
upsert_comment "$MARKER
🔁 **Auto-requeue** — this PR was dequeued from the merge queue (reason \`${REASON}\`) and has been **added back (attempt 1 of ${MAX_ATTEMPTS})**.
If it gets kicked out again it'll be re-added one more time, then left alone."
else
upsert_comment "$MARKER
🔁 **Auto-requeue** — dequeued again (reason \`${REASON}\`) and **re-added a second time (attempt ${attempt} of ${MAX_ATTEMPTS})**.
This is the **last** automatic retry. If it's kicked out once more, it won't be re-added automatically."
fi