Skip to content

feat(merge-queue): add await-and-enqueue script - #2400

Merged
ralphbean merged 3 commits into
mainfrom
feat/merge-queue-await-and-enqueue
Jun 18, 2026
Merged

feat(merge-queue): add await-and-enqueue script#2400
ralphbean merged 3 commits into
mainfrom
feat/merge-queue-await-and-enqueue

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

  • Add await-and-enqueue.sh that polls a PR until required checks pass and approvals are present, then enqueues it
  • Cross-references required checks from branch rulesets against actual check rollup — treats missing (not yet reported) checks as pending
  • Exits early if any check fails
  • Update SKILL.md with usage docs and note that gh pr merge --auto does not work with merge queues

GitHub's auto-merge API doesn't support merge queues, so this script fills that gap.

Test plan

🤖 Generated with Claude Code

Polls a PR until all required checks pass and approvals are present,
then enqueues it in the merge queue. Cross-references required checks
from branch rulesets against the actual check rollup so missing checks
(not yet reported) are treated as pending. Exits early if any check
fails.

GitHub's auto-merge API (gh pr merge --auto) does not work with merge
queues, so this script fills that gap.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
… associative arrays

Associative arrays with declare -A are fragile across shell contexts.
Move all check analysis into a single jq pass.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Site preview

Preview: https://d3becbe8-site.fullsend-ai.workers.dev

Commit: 71dc1944e6d2efecf8f652ffa60d067ce5af18ad

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:34 PM UTC · Completed 9:46 PM UTC
Commit: 1dabdc6 · View workflow run →

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [logic-error] skills/merge-queue/scripts/await-and-enqueue.sh:51 — The jq expression uses .name to identify checks and .conclusion // .status for their state, but GitHub's statusCheckRollup contains two object types: CheckRun (which has .name, .status, .conclusion) and StatusContext (which has .context, .state, and no .name/.conclusion/.status). If any required checks are reported via commit statuses (StatusContext), they will be keyed as null in the jq map, won't match required check names, and their state won't be correctly read. This causes the script to either loop forever (missing required checks are treated as pending) or silently ignore failed commit statuses.
    Remediation: Use (.name // .context) for the key and (.conclusion // .state // .status // "PENDING") for the value in the jq map construction.

  • [protected-path] skills/merge-queue/SKILL.md, skills/merge-queue/scripts/await-and-enqueue.sh — Both modified files are under skills/, which is a protected path. The PR has no linked issue and no justification for modifying governance/infrastructure files. Human approval is required for all protected-path changes.
    Remediation: File a GitHub issue explaining the rationale for modifying files under skills/ and link it from the PR description.

Medium

  • [edge-case] skills/merge-queue/scripts/await-and-enqueue.sh:39 — The script has no maximum iteration or timeout limit. If checks remain perpetually pending (stuck CI provider, webhook that never fires, a required check name that never reports), the script will poll indefinitely. Since this script is designed to be invoked by agents, an infinite loop will block the agent and consume resources without bound.
    Remediation: Add a MAX_ATTEMPTS or TIMEOUT environment variable (defaulting to a reasonable value like 60 attempts / 30 minutes) and exit with a clear error when exceeded.

Low

  • [logic-error] skills/merge-queue/scripts/await-and-enqueue.sh:28 — The branch rulesets API only returns required checks configured via the newer repository rulesets feature. Repositories using legacy branch protection rules will return an empty array, making the $missing check a no-op. The script degrades gracefully but users relying on legacy branch protection may be surprised.
    Remediation: Document this limitation in SKILL.md, or add a fallback to the legacy branch protection API.

  • [missing-authorization] skills/merge-queue/scripts/await-and-enqueue.sh — Non-trivial change (95-line script adding new automation capability) with no linked issue. While the script follows existing patterns in the merge-queue skill, the lack of authorization makes it difficult to verify whether this approach aligns with operational preferences.
    Remediation: File a GitHub issue documenting the problem this script solves and link it from the PR description.

Info

  • [edge-case] skills/merge-queue/scripts/await-and-enqueue.sh:55ACTION_REQUIRED is treated as a failure state, but it typically indicates a check needing manual intervention. Users may expect the script to wait rather than abort. Consider documenting this behavior.

  • [documentation-order] skills/merge-queue/SKILL.md:43 — The "Await and enqueue" section is placed after "Investigate dequeue reasons" but is closely related to "Enqueue a PR". Consider placing it immediately after that section.

  • [common-errors-consistency] skills/merge-queue/SKILL.md:66 — The new error entry starts with a backtick-wrapped command rather than a quoted error message in bold, which differs from the pattern of other entries in the "Common errors" section.

Previous run

Review

Findings

High

  • [logic-error] skills/merge-queue/scripts/await-and-enqueue.sh:51 — The jq expression uses .name to identify checks and .conclusion // .status for their state, but GitHub's statusCheckRollup contains two types of objects: CheckRun (which has .name, .status, .conclusion) and StatusContext (which has .context, .state, and no .name/.conclusion/.status). If any required checks are reported via commit statuses (StatusContext), they will be keyed as null in the map, won't match required check names, and their state won't be correctly read — causing the script to either loop forever or incorrectly treat them as pending.
    Remediation: Use (.name // .context) for the key and (.conclusion // .state // .status // "PENDING") for the value in the jq map construction.

  • [protected-path] skills/merge-queue/SKILL.md, skills/merge-queue/scripts/await-and-enqueue.sh — Both modified files are under skills/, which is a protected path. The PR has no linked issue and no justification for modifying governance/infrastructure files. Human approval is required for all protected-path changes.
    Remediation: File a GitHub issue explaining the rationale for modifying files under skills/ and link it from the PR description.

Medium

  • [edge-case] skills/merge-queue/scripts/await-and-enqueue.sh:39 — The script has no maximum iteration/timeout limit. If checks remain in a perpetually pending state (e.g., a required check whose webhook never fires, or a stuck CI provider), the script will poll indefinitely. For an automation script invoked by agents, this could consume resources and block the agent forever.
    Remediation: Add a MAX_ATTEMPTS or TIMEOUT parameter (defaulting to a reasonable value like 60 attempts / 30 minutes) and exit with a clear error when exceeded.

  • [missing-authorization] skills/merge-queue/scripts/await-and-enqueue.sh — Non-trivial change (95-line script adding new automation capability) with no linked issue. This is a self-contained utility script, but the lack of authorization makes it difficult to verify whether this approach aligns with operational preferences.
    Remediation: File a GitHub issue documenting the problem this script solves, the use case, and stakeholder approval. Link the issue from the PR description.

Low

  • [logic-error] skills/merge-queue/scripts/await-and-enqueue.sh:28 — The branch rulesets API only returns required checks configured via the newer repository rulesets feature. Legacy branch protection rules will return an empty array. When required is [], the $missing check is a no-op. However, the script still checks overall check rollup for failures and pending status, so it degrades gracefully.
    Remediation: Document this limitation in SKILL.md or add a fallback to the legacy branch protection API.

  • [edge-case] skills/merge-queue/scripts/await-and-enqueue.sh:55ACTION_REQUIRED is treated as a failure state, but it typically indicates a check needing manual intervention (e.g., first-time contributor workflow approval). Users may expect the script to wait rather than abort.
    Remediation: Consider treating ACTION_REQUIRED as pending with a user-facing message, or document this behavior.

Info

  • [documentation-order] skills/merge-queue/SKILL.md:40 — The "Await and enqueue" section is placed after "Investigate dequeue reasons" but is closely related to "Enqueue a PR". Consider placing it immediately after that section.

  • [common-errors-consistency] skills/merge-queue/SKILL.md:67 — The new error entry starts with a backtick-wrapped command rather than a quoted error message in bold, which differs from the pattern of other entries in the "Common errors" section.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

($checks | map({(.name): (.conclusion // .status // "PENDING")}) | add // {}) as $map |
# Check for failures
[$map | to_entries[] | select(.value | test("FAILURE|ERROR|CANCELLED|TIMED_OUT|STARTUP_FAILURE|ACTION_REQUIRED")) | .key + " (" + .value + ")"] as $failures |
# Check for pending

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] logic-error

The jq expression uses .name to identify checks and .conclusion // .status for their state, but GitHub statusCheckRollup contains two types of objects: CheckRun (with .name, .status, .conclusion) and StatusContext (with .context, .state). StatusContext entries will be keyed as null in the map, will not match required check names, and their state will not be correctly read — causing the script to loop forever or incorrectly treat them as pending.

Suggested fix: Use (.name // .context) for the key and (.conclusion // .state // .status // PENDING) for the value in the jq map construction.

echo "Waiting for checks and approvals on: $pr_url"

while true; do
# Get check rollup and review decision in one call

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] edge-case

The script has no maximum iteration/timeout limit. If checks remain in a perpetually pending state, the script will poll indefinitely. For an automation script invoked by agents, this could consume resources and block the agent forever.

Suggested fix: Add a MAX_ATTEMPTS or TIMEOUT parameter (defaulting to ~60 attempts / 30 minutes) and exit with a clear error when exceeded.

# Extract owner/repo from the PR URL
repo_nwo="$(echo "$pr_url" | sed -E 's|https://github.com/([^/]+/[^/]+)/pull/.*|\1|')"

# Fetch required status checks from branch rulesets as a JSON array

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] logic-error

The branch rulesets API only returns required checks configured via the newer rulesets feature. Legacy branch protection rules will return an empty array. The script degrades gracefully (still checks overall rollup), but could attempt to enqueue before slow-to-report checks appear.

Suggested fix: Document this limitation in SKILL.md or add a fallback to the legacy branch protection API.

[$map | to_entries[] | select(.value | test("SUCCESS|NEUTRAL|SKIPPED|COMPLETED|FAILURE|ERROR|CANCELLED|TIMED_OUT|STARTUP_FAILURE|ACTION_REQUIRED") | not) | .key] as $pending |
# Check for missing required checks
[$required[] | select(. as $r | $map | has($r) | not)] as $missing |
{failures: $failures, pending: $pending, missing: $missing}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] edge-case

ACTION_REQUIRED is treated as a failure state, but it typically indicates a check needing manual intervention. Users may expect the script to wait rather than abort.

Suggested fix: Consider treating ACTION_REQUIRED as pending with a user-facing message, or document this behavior.

@@ -37,6 +40,18 @@ Run `bash skills/merge-queue/scripts/dequeue-reason.sh <PR_NUMBER_OR_URL>` to fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[info] documentation-order

The Await and enqueue section is placed after Investigate dequeue reasons but is closely related to Enqueue a PR. Consider placing it immediately after that section.

- **"Pull request is not mergeable"** — the PR may need approvals, passing checks, or conflict resolution before it can be enqueued.
- **"Resource not accessible by integration"** — the `gh` token lacks sufficient permissions.
- **"status checks are expected"** — required checks haven't finished yet. Use `await-and-enqueue.sh` to poll and enqueue once they pass.
- **`gh pr merge --auto` fails with merge queues** — GitHub's auto-merge API does not support merge queues. Use `await-and-enqueue.sh` instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[info] common-errors-consistency

The new error entry starts with a backtick-wrapped command rather than a quoted error message in bold, which differs from the pattern of other entries in the Common errors section.

@ralphbean
ralphbean enabled auto-merge June 18, 2026 15:18
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:28 PM UTC · Completed 3:39 PM UTC
Commit: 71dc194 · View workflow run →

@ralphbean
ralphbean added this pull request to the merge queue Jun 18, 2026

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

($checks | map({(.name): (.conclusion // .status // "PENDING")}) | add // {}) as $map |
# Check for failures
[$map | to_entries[] | select(.value | test("FAILURE|ERROR|CANCELLED|TIMED_OUT|STARTUP_FAILURE|ACTION_REQUIRED")) | .key + " (" + .value + ")"] as $failures |
# Check for pending

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] logic-error

The jq expression uses .name to identify checks and .conclusion // .status for their state, but GitHub's statusCheckRollup contains two object types: CheckRun (with .name, .status, .conclusion) and StatusContext (with .context, .state). StatusContext objects will be keyed as null, won't match required check names, and their state won't be correctly read — causing infinite loops or silently ignored failures.

Suggested fix: Use (.name // .context) for the key and (.conclusion // .state // .status // "PENDING") for the value in the jq map construction.

echo "Waiting for checks and approvals on: $pr_url"

while true; do
# Get check rollup and review decision in one call

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] edge-case

The script has no maximum iteration or timeout limit. If checks remain perpetually pending, the script will poll indefinitely, blocking the invoking agent and consuming resources without bound.

Suggested fix: Add a MAX_ATTEMPTS or TIMEOUT environment variable (defaulting to ~60 attempts / 30 minutes) and exit with a clear error when exceeded.

# Extract owner/repo from the PR URL
repo_nwo="$(echo "$pr_url" | sed -E 's|https://github.com/([^/]+/[^/]+)/pull/.*|\1|')"

# Fetch required status checks from branch rulesets as a JSON array

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] logic-error

The branch rulesets API only returns required checks from the newer rulesets feature. Legacy branch protection rules return an empty array. The script degrades gracefully but this limitation is undocumented.

Suggested fix: Document this limitation in SKILL.md or add a fallback to the legacy branch protection API.

[$map | to_entries[] | select(.value | test("SUCCESS|NEUTRAL|SKIPPED|COMPLETED|FAILURE|ERROR|CANCELLED|TIMED_OUT|STARTUP_FAILURE|ACTION_REQUIRED") | not) | .key] as $pending |
# Check for missing required checks
[$required[] | select(. as $r | $map | has($r) | not)] as $missing |
{failures: $failures, pending: $pending, missing: $missing}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[info] edge-case

ACTION_REQUIRED is treated as a failure state but typically indicates a check needing manual intervention. Consider documenting this behavior.


Shows each removal event's timestamp, reason (e.g. `failed_checks`, `merge_conflict`), and the commit SHA at the time of removal.

## Await and enqueue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[info] documentation-order

The Await and enqueue section is placed after Investigate dequeue reasons but is closely related to Enqueue a PR. Consider placing it immediately after that section.

- **"Pull request is already in the merge queue"** — the PR was previously enqueued; no action needed.
- **"Pull request is not mergeable"** — the PR may need approvals, passing checks, or conflict resolution before it can be enqueued.
- **"Resource not accessible by integration"** — the `gh` token lacks sufficient permissions.
- **"status checks are expected"** — required checks haven't finished yet. Use `await-and-enqueue.sh` to poll and enqueue once they pass.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[info] common-errors-consistency

The new error entry starts with a backtick-wrapped command rather than a quoted error message in bold, differing from the pattern of other entries in the Common errors section.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 18, 2026
@ralphbean
ralphbean added this pull request to the merge queue Jun 18, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 18, 2026
@ralphbean
ralphbean added this pull request to the merge queue Jun 18, 2026
Merged via the queue into main with commit 8271187 Jun 18, 2026
12 checks passed
@ralphbean
ralphbean deleted the feat/merge-queue-await-and-enqueue branch June 18, 2026 16:26
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 4:31 PM UTC · Completed 4:38 PM UTC
Commit: 71dc194 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #2400 — feat(merge-queue): add await-and-enqueue script

Timeline

  1. 2026-06-17 21:29 UTC — ralphbean opens human-authored PR adding await-and-enqueue.sh (95 lines) and SKILL.md updates
  2. 2026-06-17 21:34–21:46 UTC — First review agent run completes with CHANGES_REQUESTED, flagging a high-severity logic error (StatusContext vs CheckRun jq handling), a medium edge-case (no timeout), and several low/info findings
  3. 2026-06-18 06:57 UTC — rh-hemartin approves with no comment body
  4. 2026-06-18 15:23 UTC — Merge commit from main into the feature branch
  5. 2026-06-18 15:28–15:39 UTC — Second review agent run triggers on the merge commit, produces nearly identical CHANGES_REQUESTED with the same findings
  6. 2026-06-18 16:26 UTC — PR merged with both bot CHANGES_REQUESTED reviews still active

Review quality assessment

The review agent performed well. The high-severity finding about StatusContext handling is a confirmed real bug: GitHub's statusCheckRollup contains both CheckRun objects (with .name, .conclusion) and StatusContext objects (with .context, .state). The script uses .name uniformly, causing StatusContext checks to be keyed as null and their state to fall through to "PENDING" — which could cause infinite polling or premature enqueue. The medium finding about no timeout limit is also valid.

Issues identified (all already tracked)

All workflow improvement opportunities found are covered by existing open issues:

  • Duplicate review after merge-from-main commit — The second review run was pure waste, producing identical findings for ~11 min of agent time. Covered by #1282, #1896, #1356, #1285.
  • Real high-severity findings merged without resolution — No mechanism exists to auto-file tracking issues when medium+ findings are overridden by human approval. Covered by #1956 and #870.
  • Protected-path noise on human PRs — The bot flagged skills/ as a protected path on a human-authored PR, which adds noise. Covered by #1551.

Conclusion

No new proposals — all improvement opportunities are well-covered by existing issues. The review agent's signal quality was high (caught a real bug), but the duplicate run after a merge-from-main commit wasted tokens. Prioritizing #1282/#1896 (skip re-review on merge-only commits) and #1956 (auto-file tracking issues for unresolved findings) would have the highest impact for workflows like this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants