Skip to content

fix(sdlc): native merge queue core drain — arm-only autoqueue + all-green + governance-gate#3849

Merged
ryanklee merged 4 commits into
mainfrom
theta/reform-nmq-core-drain-20260601
Jun 1, 2026
Merged

fix(sdlc): native merge queue core drain — arm-only autoqueue + all-green + governance-gate#3849
ryanklee merged 4 commits into
mainfrom
theta/reform-nmq-core-drain-20260601

Conversation

@ryanklee
Copy link
Copy Markdown
Collaborator

@ryanklee ryanklee commented Jun 1, 2026

Reform native merge queue — the core drain (sub-PR 2 of the migration)

cc-task: reform-native-merge-queue-20260601
AuthorityCase: CASE-SDLC-REFORM-001
Parent spec: coordination-reform-master-design-2026-05-30.md

Activates the GitHub-native merge queue as the sole drain and demotes the local
cc-pr-autoqueue to arm-only, so armed+green PRs stop stranding. The native
queue already drains armed PRs (verified live: #3840/#3845/#3847/#3848 merged
through gh-readonly-queue); the loss was the local autoqueue reimplementing a
queue with direct --merge/dequeue mutations that fight GitHub's batching. This
PR makes the autoqueue issue exactly one idempotent gh pr merge --auto --squash
to arm, and lets GitHub own batching, speculative branches, auto-rebase, and
bisect-on-failure.

Changes

  • scripts/cc-pr-autoqueue.pymerge_pr arms with --auto --squash for
    both the queue and enable_auto_merge decisions (was direct --merge /
    --auto --merge). Arm-only; idempotent; GitHub owns the rest.
  • scripts/cc-pr-autoqueue.pySHARED_FILE_EPIC_PARENT_SPECS emptied: the
    native queue's speculative branches serialize shared-file contention, so the
    CLOG pre-admission affinity hold is redundant. Mechanism preserved via the
    explicit epic_serialize field.
  • .github/workflows/ci.yml — new all-green aggregate job (needs: the
    required jobs + test-full-shard, if: always(), passes on success/skipped).
    Collapses the five literal required contexts into one, killing the
    required-context-name-mismatch that can stall the queue. Fast/slow split
    preserved (test-full-shard stays merge_group-only ⇒ all-green still passes
    on PR events).
  • .github/workflows/governance-gate.yml — new dedicated governance check
    that verifies the server-side hapax/autoqueue-admission proof (via
    queue-admission-proof-check.py) on pull_request + merge_group. Runner-side
    vault reads are infeasible (vault is local-only), so this reuses the proof the
    vault-reading local autoqueue already posts. Enforces only for enqueued/armed
    PRs ⇒ it never wedges normal PR CI.

Follow-up (ruleset, requires admin verification — done post-merge)

  • Swap branch-protection required contexts lint/test/typecheck/web-build/vscode-buildall-green.
  • Add governance-gate (and the existing pr-admission proof) to required contexts.
  • Remaining task sub-PRs: G3 idempotent admission writes, G4/G5 drain-repair watcher, G7 arg-aware gate.

Built off main in an isolated worktree; the canonical theta worktree is
occupied by a different in-flight task.

Summary by CodeRabbit

  • New Features

    • Added CI aggregation check that validates all required tests complete successfully before allowing merges.
    • Added governance validation workflow for pull request admissions.
  • Improvements

    • Updated auto-merge behavior to leverage GitHub's native merge queue for improved batching, speculative builds, and auto-rebase capabilities.

…zes shared-file contention

The native GitHub merge queue serializes shared-file contention through its
speculative gh-readonly-queue branches (auto-rebase + bisect-on-failure), so the
pre-admission affinity hold for the CLOG decompose epic is redundant. Empty the
registry; the hold mechanism remains available via the explicit `epic_serialize`
frontmatter field. The four affinity mechanism tests are rewired to that field,
and a regression test asserts a CLOG `parent_spec` alone no longer holds.

Part of the reform-native-merge-queue core drain (demote the local autoqueue to
arm-only; let GitHub own batching/dequeue/bisect).

Task: reform-native-merge-queue-20260601
AuthorityCase: CASE-SDLC-REFORM-001

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors PR queuing and CI validation by introducing CI result aggregation, server-side governance validation, and shifting auto-queue merging from direct command management to GitHub's native merge-queue arm-only flow with updated shared-file epic serialization.

Changes

CI Infrastructure and Governance

Layer / File(s) Summary
CI result aggregation job and manifest
.github/workflows/ci.yml, hooks/gate-manifest.yaml
Adds all-green aggregation job that collects results from required CI jobs (lint, typecheck, test, test-full-shard, web-build, vscode-build) and fails if any result is not success or skipped. Registers job in gate manifest.
Server-side governance validation workflow
.github/workflows/governance-gate.yml
Introduces governance-gate workflow triggered on PR queue admission and merge-group events. Enforces read-only permissions and runs queue-admission-proof-check.py to validate governance proof via commit status.

Auto-queue Merge Strategy and Shared-file Epic

Layer / File(s) Summary
Auto-queue arm-only merge strategy and shared-file epic update
scripts/cc-pr-autoqueue.py
Shifts from direct --merge management to arm-only GitHub native merge-queue flow using gh pr merge --auto --squash. Updates docstring to reflect new model. Changes SHARED_FILE_EPIC_PARENT_SPECS from untyped dict with hard-coded entry to typed dict[str, str], removing the entry and noting that local pre-queue affinity holds are no longer needed.
Test suite updates for new merge behavior and shared-file epic opt-in
tests/test_cc_pr_autoqueue.py
Updates all test assertions across governed PR queuing, auto-merge, stabilization, storm-mode, and release flows to expect gh pr merge ... --auto --squash. Introduces explicit _CLOG_EPIC identifier and switches shared-file affinity tests to opt-in via epic_serialize frontmatter field. Adds regression test ensuring parent_spec matching alone no longer triggers affinity holds.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hapax-systems/hapax-council#3844: Both PRs modify SHARED_FILE_EPIC_PARENT_SPECS constant and shared-file epic serialization logic in auto-queue script and tests, with main PR switching to explicit opt-in via frontmatter field.
  • hapax-systems/hapax-council#3637: Main PR's auto-queue arm-only gh pr merge --auto --squash flow directly interfaces with the retrieved PR's hapax/autoqueue-admission commit-status proof publishing that controls queue/arm mutation admission.
  • hapax-systems/hapax-council#3419: Both PRs modify .github/workflows/ci.yml CI coordination—retrieved PR adds test-full-shard gate, main PR adds all-green aggregator that requires both test and test-full-shard job results.

Poem

🐰 The queue now hops with gentle grace,
Armed but patient in the race.
Gates stand guard with proofs divine,
While epics serialize in line.
Green lights gleam, all artifacts shine. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: demoting the local autoqueue to arm-only mode and activating GitHub's native merge queue with the new all-green and governance-gate mechanisms.
Description check ✅ Passed The PR description covers the required AuthorityCase sections, provides a detailed summary of changes and their rationale, and includes a comprehensive test plan reference and follow-up tasks. All key structural elements align with the template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch theta/reform-nmq-core-drain-20260601

Comment @coderabbitai help to get the list of available commands and usage tips.

ryanklee and others added 3 commits June 1, 2026 17:47
…quash)

The native GitHub merge queue already drains armed PRs; the loss was the local
autoqueue reimplementing a queue with direct `--merge` adds and dequeue mutations
that raced GitHub's own batching and stranded PRs. Demote the reconciler's sole
positive mutation to one idempotent `gh pr merge --auto --squash` (arm-only), for
both the `queue` and `enable_auto_merge` decisions. GitHub then owns batching,
speculative gh-readonly-queue branches, auto-rebase, and bisect-on-failure.
`--squash` matches the queue's configured merge method. Corrective `disable-auto`
/ `dequeue` paths for now-blocked PRs are unchanged.

Tests: command assertions updated to `--auto --squash`; 47 pass.

Task: reform-native-merge-queue-20260601
AuthorityCase: CASE-SDLC-REFORM-001

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapses the five literal required checks (lint/test/typecheck/web-build/
vscode-build) into one aggregate job so the native merge queue can't be
permanently stalled by a required-context-name mismatch. if: always() plus
treating skipped as OK preserves the fast/slow split — test-full-shard is
merge_group-only and is skipped on pull_request events, so all-green still
passes there. Results read from an env var (injection-safe pattern).

hooks/gate-manifest.yaml: register the new job so the gate-manifest wiring
check stays green (the manifest pins the canonical ci.yml job set).

Post-merge admin step: swap branch-protection required contexts -> all-green.

Task: reform-native-merge-queue-20260601
AuthorityCase: CASE-SDLC-REFORM-001

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sion proof)

Part 3 of the native-merge-queue reform. Makes governance a server-side gate so
enqueueing itself is governed: a PR enters the merge queue only when its linked
cc-task is governance-releasable (stage >= S7_RELEASE, release_authorized,
authority_case). GitHub-hosted runners cannot read the local cc-task vault, so
the gate verifies the PROOF the vault-reading local autoqueue posts — the
hapax/autoqueue-admission commit status — via the existing
queue-admission-proof-check.py. Enforced only for enqueued/auto-merge PRs and
merge_group, so ordinary PR CI is never wedged. Distinct, clearly-named context
(vs pr-admission.yml's freeze governor) so it can be made a required status
check in the main-merge-queue ruleset.

Post-merge admin step: add governance-gate to the ruleset required contexts
after verifying it goes green on a live armed PR.

Task: reform-native-merge-queue-20260601
AuthorityCase: CASE-SDLC-REFORM-001

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ryanklee ryanklee marked this pull request as ready for review June 1, 2026 23:19
@ryanklee ryanklee added this pull request to the merge queue Jun 1, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 588a25c459

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml
Comment on lines +1203 to +1204
case "$r" in
success | skipped) ;;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fail all-green when required jobs are skipped unexpectedly

Because all-green does not depend on docs_only_filter or post_merge_duplicate_filter, a failure in either setup job causes downstream jobs like lint, typecheck, web-build, and vscode-build to be skipped; this loop then treats every skipped result as acceptable and reports the aggregate check green. Once branch protection is switched to require only all-green, a broken filter/duplicate-detection step can therefore let a PR or merge-group pass without running the required CI jobs. Please either include the setup jobs in the aggregate or only allow skipped for the explicitly expected cases.

Useful? React with 👍 / 👎.

Comment on lines +119 to +123
SHARED_FILE_EPIC_PARENT_SPECS: dict[str, str] = {
# parent_spec basename -> serialized-epic id (the shared file it contends on).
#
# Emptied 2026-06-01 (task reform-native-merge-queue): the native GitHub merge
# queue serializes shared-file contention through its speculative
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep parent-spec hold for unqueued epic work

This registry is now empty, so existing CLOG tasks that only carry the documented parent_spec no longer participate in shared_file_epic_affinity_blockers unless every note has already been migrated to epic_serialize. The native merge queue can serialize PRs that have reached the queue, but it cannot see an in-progress sibling with no PR yet; in that common case a ready CLOG PR can be admitted while another lane is still editing the same shared file, reintroducing the conflict this hold was designed to prevent.

Useful? React with 👍 / 👎.

Comment on lines +54 to +56
python3 scripts/queue-admission-proof-check.py \
--repo "$REPO" \
--event-path "$GITHUB_EVENT_PATH"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Run the proof verifier from a protected revision

This required gate checks out the candidate ref and then executes scripts/queue-admission-proof-check.py from that same checkout, so any PR that changes the verifier can make governance-gate pass without a valid hapax/autoqueue-admission status. For a server-side admission gate that is meant to constrain queued and merge-group PRs, the verifier needs to come from a protected base revision or pinned action rather than from the code being admitted.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
.github/workflows/governance-gate.yml (1)

44-44: 💤 Low value

Consider upgrading to actions/checkout@v6 for consistency.

The CI workflow uses actions/checkout@v6, but this workflow uses @v4. While both versions work, using consistent versions across workflows reduces maintenance burden and potential behavioral differences.

📦 Proposed version alignment
-        uses: actions/checkout@v4
+        uses: actions/checkout@b5297f6833c67f732b6632c5c4dbfc4e592b0cb2  # v6.2.0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/governance-gate.yml at line 44, Update the checkout action
to the consistent version by replacing the occurrence of "uses:
actions/checkout@v4" in the governance-gate.yml workflow with "uses:
actions/checkout@v6" so the workflow matches the other CI workflows and avoids
version drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/governance-gate.yml:
- Around line 43-46: The Checkout step currently uses the floating tag
actions/checkout@v4 and leaves credential persistence defaulted; update the
Checkout step (the use: actions/checkout@v4 entry) to pin the action to a
repository-specific commit SHA (replace the v4 tag with the approved commit SHA
for actions/checkout) and add the with: persist-credentials: false setting (you
can keep fetch-depth: 1) so credentials are not persisted to the workspace or
artifacts.

In `@scripts/cc-pr-autoqueue.py`:
- Around line 910-918: The arming command for auto-merge in the block where
decision.action is ("enable_auto_merge","queue") currently extends cmd with
["--auto","--squash"] without binding to the proven head SHA; update the call
site that builds cmd (where cmd.extend is used) to also append
"--match-head-commit" followed by the admission proof SHA from
decision.pr.head_sha (the same SHA used by set_autoqueue_admission_status()) so
GitHub will require the PR head to match the proof before arming.

---

Nitpick comments:
In @.github/workflows/governance-gate.yml:
- Line 44: Update the checkout action to the consistent version by replacing the
occurrence of "uses: actions/checkout@v4" in the governance-gate.yml workflow
with "uses: actions/checkout@v6" so the workflow matches the other CI workflows
and avoids version drift.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f60e62f9-4b59-4024-bab5-99ccad3f8746

📥 Commits

Reviewing files that changed from the base of the PR and between 671b815 and 588a25c.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .github/workflows/governance-gate.yml
  • hooks/gate-manifest.yaml
  • scripts/cc-pr-autoqueue.py
  • tests/test_cc_pr_autoqueue.py

Comment on lines +43 to +46
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin action to commit SHA and disable credential persistence.

The checkout action should be pinned to a specific commit SHA per repository policy and should set persist-credentials: false to prevent credential leakage through artifacts.

🔒 Proposed security fix
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
         with:
           fetch-depth: 1
+          persist-credentials: false

As per coding guidelines and static analysis (zizmor): actions must be pinned to commit hashes per blanket policy, and credentials should not persist to prevent exposure through artifacts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 1
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 43-46: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 44-44: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/governance-gate.yml around lines 43 - 46, The Checkout
step currently uses the floating tag actions/checkout@v4 and leaves credential
persistence defaulted; update the Checkout step (the use: actions/checkout@v4
entry) to pin the action to a repository-specific commit SHA (replace the v4 tag
with the approved commit SHA for actions/checkout) and add the with:
persist-credentials: false setting (you can keep fetch-depth: 1) so credentials
are not persisted to the workspace or artifacts.

Comment on lines +910 to +918
if decision.action in ("enable_auto_merge", "queue"):
# Arm-only (task reform-native-merge-queue): the local autoqueue's sole
# positive mutation is to ARM auto-merge with one idempotent command.
# GitHub's native merge queue then owns batching, speculative
# gh-readonly-queue branches, auto-rebase, and bisect-on-failure — we no
# longer issue a direct `--merge` (which raced the queue's own management).
# Re-arming an already-armed PR is a no-op; `--squash` matches the queue's
# configured merge method.
cmd.extend(["--auto", "--squash"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In the official GitHub CLI documentation for gh pr merge, is the --match-head-commit flag supported, and does it prevent merging or enabling auto-merge when the PR head SHA has changed?

💡 Result:

Yes, the --match-head-commit flag is supported in the official GitHub CLI documentation for the gh pr merge command [1][2][3]. This flag requires a commit SHA as an argument, which serves as a safety check: the pull request's head must match the provided SHA to allow the merge to proceed [1][4][2]. If the pull request head SHA has changed (i.e., it does not match the SHA provided), the command will prevent the merge [4][5]. While the documentation notes that the flag ensures the branch has not changed out from under the user [4], its primary function is to block the operation if the specified HEAD condition is not met. It is not explicitly stated in the manual that this flag disables the enabling of auto-merge, but rather that it serves as a constraint to allow or block the merge process based on the current state of the pull request head [1][2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show context around the cited lines
sed -n '860,960p' scripts/cc-pr-autoqueue.py | nl -ba | sed -n '1,140p'

# Find and inspect set_autoqueue_admission_status()
rg -n "def set_autoqueue_admission_status|set_autoqueue_admission_status\(" -S scripts/cc-pr-autoqueue.py
python3 - <<'PY'
import re, itertools, pathlib
path = pathlib.Path("scripts/cc-pr-autoqueue.py")
txt = path.read_text(encoding="utf-8", errors="replace").splitlines()
# locate function start
start = None
for i,line in enumerate(txt):
    if re.match(r"def set_autoqueue_admission_status\s*\(", line):
        start = i
        break
if start is None:
    raise SystemExit("set_autoqueue_admission_status not found")
# print ~200 lines from start
for j in range(start, min(len(txt), start+220)):
    print(f"{j+1:5d}:{txt[j]}")
PY

# Search for gh pr merge invocation / cmd construction near that block
rg -n "gh pr merge|pr merge|--auto|--squash|merge queue" scripts/cc-pr-autoqueue.py

Repository: hapax-systems/hapax-council

Length of output: 114


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show context around the cited region without relying on `nl`
echo "=== scripts/cc-pr-autoqueue.py lines 880-960 (approx) ==="
sed -n '880,960p' scripts/cc-pr-autoqueue.py | sed -n '1,120p'

echo
echo "=== Locate set_autoqueue_admission_status definition ==="
rg -n "def set_autoqueue_admission_status\s*\(" scripts/cc-pr-autoqueue.py

echo
echo "=== Print set_autoqueue_admission_status body (first ~240 lines) ==="
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("scripts/cc-pr-autoqueue.py")
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
start = None
for i,l in enumerate(lines):
    if re.match(r"def set_autoqueue_admission_status\s*\(", l):
        start = i
        break
assert start is not None
end = min(len(lines), start+240)
for idx in range(start, end):
    print(f"{idx+1:5d}:{lines[idx]}")
PY

echo
echo "=== Search for gh pr merge command construction/execution ==="
rg -n "gh pr merge|--auto|--squash|--match-head-commit|subprocess\.run|check_call|Popen" scripts/cc-pr-autoqueue.py

echo
echo "=== Extract the specific if block around cmd.extend([\"--auto\", \"--squash\"]) ==="
rg -n "cmd\.extend\(\[\s*\"--auto\"\s*,\s*\"--squash\"\s*\]\)" -n scripts/cc-pr-autoqueue.py || true
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("scripts/cc-pr-autoqueue.py")
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
for i,l in enumerate(lines):
    if 'cmd.extend(["--auto", "--squash"])' in l.replace(" ", "") or 'cmd.extend(["--auto", "--squash"])' in l:
        # print ~25 lines around
        s=max(0,i-20); e=min(len(lines), i+25)
        print(f"Match near line {i+1}")
        for j in range(s,e):
            print(f"{j+1:5d}:{lines[j]}")
PY

Repository: hapax-systems/hapax-council

Length of output: 16441


🌐 Web query:

gh pr merge --match-head-commit --auto behavior when PR head changes

💡 Result:

Behavior summary for gh pr merge --match-head-commit <SHA> --auto when the PR head changes: 1) --auto does not “track” the PR head for you. When you run gh pr merge --auto, the CLI only enables auto-merge (or queues it) based on the PR state it reads and on the merge payload it sends at that moment; it doesn’t re-issue the merge request automatically if the PR head commit changes later. This is evidenced by gh pr merge’s documented intent for --auto (“Automatically merge only after necessary requirements are met”) and the implementation discussion where the CLI sets auto-merge based on fetched PR state and then sends that directly through the merge payload without continuing to re-evaluate on later PR updates.[1][2] 2) --match-head-commit is a hard precondition: the head must still match the specified commit SHA. The --match-head-commit SHA flag is explicitly defined as “Commit SHA that the pull request head must match to allow merge”.[1] Therefore, if you specify a head SHA and then the PR head changes (new commits are pushed), GitHub will no longer satisfy that precondition, so the already-configured auto-merge/merge attempt will not proceed for that earlier head SHA. 3) Practical effect: you should expect auto-merge to stall/fail for the prior head SHA, and you must re-run the command (or otherwise update the auto-merge configuration) for the new head. Because the head-matching is required “to allow merge”[1], the common outcome when the PR head diverges is that merge will not be permitted until the configuration matches the latest head again (e.g., by reissuing gh pr merge with --match-head-commit set to the new head SHA). 4) Related nuance: --auto can be backed by merge queue behavior. If the target base branch uses a merge queue, gh pr merge will “enable auto-merge” when required checks have not passed yet, or “add the pull request to the merge queue” when required checks have passed.[1] In either case, --match-head-commit still represents the required head SHA for the merge to be allowed.[1] Bottom line If the PR head changes after you run gh pr merge --match-head-commit <oldSHA> --auto, GitHub will not merge that PR for <oldSHA> because the head no longer matches the required SHA.[1] Re-run gh pr merge --auto --match-head-commit <newSHA> (or omit --match-head-commit if you don’t need this safety gate) so the precondition matches the updated head commit.

Citations:


🌐 Web query:

cli.github.com manual gh pr merge --match-head-commit requires head match fail if head changed

💡 Result:

If the PR head commit changes after you captured the SHA and you run gh pr merge with --match-head-commit , the merge will not proceed because that flag specifies “Commit SHA that the pull request head must match to allow merge” (i.e., it acts as a precondition). The official gh pr merge manual documents the flag semantics but does not describe the exact wording of the error message you’ll see, only that the merge is allowed only when the head matches the provided SHA [1]. References: [1] https://cli.github.com/manual/gh_pr_merge

Citations:


Bind the auto-merge/merge-queue arming command to the classified head SHA.

scripts/cc-pr-autoqueue.py currently enables auto-merge/queue via gh pr merge <pr-number> --auto --squash (keyed only by PR number), while set_autoqueue_admission_status() posts the admission proof to decision.pr.head_sha. If the PR head changes between those steps, the arming request can target a different head; add --match-head-commit <head_sha> so GitHub requires the PR head to still match the proof SHA to allow the operation.

Suggested change
     if decision.action in ("enable_auto_merge", "queue"):
         # Arm-only (task reform-native-merge-queue): the local autoqueue's sole
         # positive mutation is to ARM auto-merge with one idempotent command.
         # GitHub's native merge queue then owns batching, speculative
         # gh-readonly-queue branches, auto-rebase, and bisect-on-failure — we no
         # longer issue a direct `--merge` (which raced the queue's own management).
         # Re-arming an already-armed PR is a no-op; `--squash` matches the queue's
         # configured merge method.
         cmd.extend(["--auto", "--squash"])
+        if decision.pr.head_sha:
+            cmd.extend(["--match-head-commit", decision.pr.head_sha])
     elif decision.action == "disable_auto_merge":
         cmd.append("--disable-auto")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if decision.action in ("enable_auto_merge", "queue"):
# Arm-only (task reform-native-merge-queue): the local autoqueue's sole
# positive mutation is to ARM auto-merge with one idempotent command.
# GitHub's native merge queue then owns batching, speculative
# gh-readonly-queue branches, auto-rebase, and bisect-on-failure — we no
# longer issue a direct `--merge` (which raced the queue's own management).
# Re-arming an already-armed PR is a no-op; `--squash` matches the queue's
# configured merge method.
cmd.extend(["--auto", "--squash"])
if decision.action in ("enable_auto_merge", "queue"):
# Arm-only (task reform-native-merge-queue): the local autoqueue's sole
# positive mutation is to ARM auto-merge with one idempotent command.
# GitHub's native merge queue then owns batching, speculative
# gh-readonly-queue branches, auto-rebase, and bisect-on-failure — we no
# longer issue a direct `--merge` (which raced the queue's own management).
# Re-arming an already-armed PR is a no-op; `--squash` matches the queue's
# configured merge method.
cmd.extend(["--auto", "--squash"])
if decision.pr.head_sha:
cmd.extend(["--match-head-commit", decision.pr.head_sha])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/cc-pr-autoqueue.py` around lines 910 - 918, The arming command for
auto-merge in the block where decision.action is ("enable_auto_merge","queue")
currently extends cmd with ["--auto","--squash"] without binding to the proven
head SHA; update the call site that builds cmd (where cmd.extend is used) to
also append "--match-head-commit" followed by the admission proof SHA from
decision.pr.head_sha (the same SHA used by set_autoqueue_admission_status()) so
GitHub will require the PR head to match the proof before arming.

Merged via the queue into main with commit 8c4b7e1 Jun 1, 2026
37 checks passed
@ryanklee ryanklee deleted the theta/reform-nmq-core-drain-20260601 branch June 1, 2026 23:31
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.

1 participant