Skip to content

fix(source-control): hold pr-verify-failed pull requests in pr-merge - #10676

Merged
kyle-sexton merged 2 commits into
mainfrom
fix/merge-skip-pr-verify-failed
Oct 28, 2026
Merged

kyle-sexton merged 2 commits into
mainfrom
fix/merge-skip-pr-verify-failed

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: companion to Automation Lanes' fix/verify-fail-label-hold, which has pr-verify label a person's PR pr-verify-failed on a FAIL.

Summary

ci-status enforces pr-verify-failed only in the merge-queue step. On the pull_request run it would deadlock pr-verify, which judges a head only after ci-status passes (.github/workflows/pr-verify.yml:74). So a labeled PR shows a green ci-status, pr-merge arms and enqueues it, and the queue ejects it, in a loop. This PR makes pr-merge hold the label in every place it already holds do-not-merge.

Fix

pr-merge has no config-read skip-label list today. do-not-merge is a constant in two places, and this PR adds pr-verify-failed beside it in both:

  • plugins/source-control/skills/babysit-prs/scripts/babysit_merge_policy.py MERGE_HOLD_LABELS. It feeds merge_hold_label, which covers the gate's blockers (no merge or enqueue) and pr-merge's candidate prefilter (babysit_merge_prefilter.py). It also feeds adoption_refusal (adopting a person's idle PR) and entry_hold, the queue sweep that removes a queued entry once it carries a hold.
  • .github/scripts/pr-merge/enable-auto-merge.mjs HOLD_LABELS, the mirror read by holdReason. That job does not arm a labeled PR, and it disarms one that is already armed, on scheduled runs and on event runs. It arms only lanes App PRs, so for a person's PR the Python list above is what stops the loop.

Left unchanged on purpose:

  • The labels-absent lane filters in docs/conventions/pr-pipeline.yaml: they would also stop the lanes that turn the FAIL into a PASS.
  • babysit_merge_block_labels / --block-labels: an additive per-tier config, not the merge hold.
  • The draft-recovery skip in babysit_whole_review.py, check-wip-cap and apply-patch: none of them arms or enqueues.
  • gate-check.mjs and superseded.mjs: neither has a label skip.

The next such label still takes an edit to both constants.

Verification

The new tests were added first, and all four failed against the unchanged constants:

  • enable-auto-merge.test.mjs, "a pr-verify-failed pull request is not armed, and an armed one is disarmed": schedule and event runs.
  • test_babysit_merge_policy.py, EntryHold.test_the_pr_verify_failed_label and AdoptIdleOwnerPullRequests.test_the_pr_verify_failed_label_holds.
  • test_babysit_merge_prefilter.py, test_a_pr_verify_failed_candidate_is_dropped.

The existing do-not-merge tests are untouched and still pass. After the fix, the .github/scripts/pr-merge node suite passed 608/608 and the babysit-prs Python suite passed 3344.

Related

🤖 Generated with Claude Code

pr-verify labels a person's pull request pr-verify-failed on a FAIL, and
ci-status enforces that label only in the merge queue. pr-merge armed and
enqueued it anyway, and the queue ejected it, in a loop. Add the label to
both merge-hold lists, beside do-not-merge, so the gate, its prefilter,
adoption, the queue sweep and enable-auto-merge all hold it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 28, 2026 15:03
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T15:10:02.222105Z 0efc4d5 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

enable-auto-merge.mjs HOLD_LABELS mirrors babysit_merge_policy
MERGE_HOLD_LABELS by hand. Export it and compare the two under node in
test_merge_holds_sync.py, beside the other JS/Python parity checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed f85be444eaa1d8dab142276690a21c57286f901c: 0 finding(s), 0 as inline comments.

Incremental review of the changes since 0efc4d5 found no security issues.

  • .github/scripts/pr-merge/enable-auto-merge.mjs: the only change is exporting HOLD_LABELS. Its contents are unchanged (do-not-merge, pr-verify-failed), so no merge hold is weakened or narrowed. The module already exports many constants, and its main() runs only behind the import.meta.url === pathToFileURL(process.argv[1]) guard at line 1567. Importing it from a test therefore has no side effects. The array is Object.freezed, so an importer cannot mutate it.
  • plugins/source-control/skills/babysit-prs/scripts/tests/test_merge_holds_sync.py: this is a new test that compares the JS HOLD_LABELS with policy.MERGE_HOLD_LABELS. It spawns node with fixed arguments, and json.dumps(module.as_uri()) builds the module path from the repository root, not from untrusted input. It is test-only code and guards against the two hold lists drifting apart.

No trust-control widening, no manifest changes, and no hold needed. The change spends no billable resources.

critical-findings: 0

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed f85be444eaa1d8dab142276690a21c57286f901c: 0 finding(s), 0 as inline comments.

Incremental review of the change since 0efc4d5: no findings.

  • .github/scripts/pr-merge/enable-auto-merge.mjs (trusted path): HOLD_LABELS is now exported. The value is unchanged and the array is still frozen. The file's main() only runs behind the import.meta.url === pathToFileURL(process.argv[1]) guard, so importing the module from the new test starts no run and has no side effects. The export does not widen any gate.
  • plugins/source-control/skills/babysit-prs/scripts/tests/test_merge_holds_sync.py: the new TheHoldLabelsAreTheGates test imports the module under node and compares HOLD_LABELS with policy.MERGE_HOLD_LABELS (("do-not-merge", "pr-verify-failed")). The comparison is by order as well as content. Both lists are in that order today. It follows the sibling tests' skip-if-no-node pattern and closes the "next label takes an edit to both constants" gap the PR description names. I read both sides and did not run the test, so this is a static read.

Spend: no cost hold. The change touches no paid plans or billing.

@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit f7371d8 Oct 28, 2026
42 checks passed
@kyle-sexton
kyle-sexton deleted the fix/merge-skip-pr-verify-failed branch October 28, 2026 15:22
@github-actions

Copy link
Copy Markdown
Contributor

Post-merge mutation testing

Killed 3, survived 3, timeout 0, no coverage 0, invalid 0, not run 2.
Covered-code score 50%; mutation score 50%.

File Killed Survived Timeout No coverage Invalid Not run
.github/scripts/pr-merge/enable-auto-merge.mjs 0 0 0 0 0 1
plugins/source-control/scripts/worktree-claim.sh 3 3 0 0 0 0
plugins/source-control/skills/babysit-prs/scripts/babysit_merge_policy.py 0 0 0 0 0 1

Surviving mutants

Location Operator Disposition Cause
plugins/source-control/scripts/worktree-claim.sh:151 SBR productive input-gap
plugins/source-control/scripts/worktree-claim.sh:160 UOI productive input-gap
plugins/source-control/scripts/worktree-claim.sh:161 SBR productive input-gap

The run's summary has the full report.

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