ci: make reviewer merge-holds stick against auto-merge re-arming - #313
ci: make reviewer merge-holds stick against auto-merge re-arming#313dieterolson wants to merge 2 commits into
Conversation
Fleet automation re-arms auto-merge on PRs a reviewer disarmed (measured at 6-8s in Gasification_Model), so a manual disarm cannot hold a PR back. Adds a guard that revokes auto-merge on held PRs, refuses PRs deleting tracked files without acknowledgement, and converts to draft after repeated re-arms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
💡 Codex ReviewDrake_Models/.github/workflows/Merge-Hold-Guard.yml Lines 134 to 136 in bf02cde gh pr list sweep
In scheduled and input-less dispatch runs, this enumerates up to 200 open PRs via AGENTS.md reference: AGENTS.md:L96-L96 Drake_Models/.github/workflows/Merge-Hold-Guard.yml Lines 232 to 234 in bf02cde If the files endpoint returns a transient, authentication, or rate-limit error, this replaces the result with an empty file and interprets the PR as having no deletions. On an armed PR with no other signal, the workflow then leaves auto-merge enabled, defeating the deletion guard during exactly the event that must revoke it; record the failure and fail the enforcement run rather than treating it as an empty result. The suppression also continues issuing API calls after a possible rate-limit response, contrary to the repository's required handling. AGENTS.md reference: AGENTS.md:L110-L110 Drake_Models/.github/workflows/Merge-Hold-Guard.yml Lines 215 to 217 in bf02cde A commit's committer timestamp is not the time it was pushed to the PR branch. If a contributor responds to a hold by pushing or force-updating to an existing older commit, Drake_Models/.github/workflows/Merge-Hold-Guard.yml Lines 225 to 226 in bf02cde This counter treats every ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
`do-not-automate` is already the fleet-wide convention for "this work must not be automated" (shared_scripts/agent_identity.DO_NOT_AUTOMATE_LABEL). Honouring it here keeps one vocabulary instead of a parallel one, and collapses the per-label checks into a single list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Problem
Fleet automation re-arms GitHub auto-merge on pull requests that a reviewer has deliberately disarmed, which defeats the only lightweight mechanism available for holding a dangerous PR back.
Measured in
Gasification_Modelon 2026-08-14 (all events attributed todieterolson,type=User, i.e. the owner's credentials rather than a bot):PR #4709 is why this matters rather than merely annoying: it had auto-merge armed on a diff that deleted 13 files present on
mainand grewSPEC.mdfrom 5,084 to 60,863 lines.The re-armer is not a workflow in this repository. No workflow in
Gasification_Model,Tools,Tools_Private,Drake_Models,MuJoCo_Models,ControlsorMaxwell_Daemoncallsgh pr merge --autoorenablePullRequestAutoMerge. The arming comes from agent sessions running under the owner'sghcredentials, driven by thefleet-pr-queueautomation inRepository_Management/config/codex_fleet_automations.json, whose prompt instructs them to "enable auto-merge for the highest-confidence item". Because those sessions live outside any single repo, a repo-side guard is the only enforcement point that cannot be bypassed by editing an agent prompt.What this adds
.github/workflows/Merge-Hold-Guard.yml. It only ever removes auto-merge — it never merges, never arms, never pushes.Hold signals (any one is sufficient):
do-not-mergeorblockedlabeldeletions-acknowledgedlabel, or aDeletions-Acknowledged: yesline in the PR body)Signal 3 deliberately ignores bot actors so the guard's own revocations can never manufacture a hold, and a genuine push clears the hold naturally.
Triggers:
pull_request_targetincluding theauto_merge_enabledactivity type, so a held PR is disarmed seconds after any re-arm; plus a20,50 * * * *sweep over armed PRs as a backstop, offset off the hour so it does not collide with the fleet pass.Escalation. A plain revoke cannot win a 6-second re-arm race on its own. After 2 revocations against the same head commit, the guard converts the PR to a draft. This was verified empirically against
Tools#4453:GitHub refuses to arm auto-merge on a draft, so the hold becomes enforceable at the API level rather than advisory.
gh pr ready <n>reverses it deliberately.The guard also applies
do-not-mergeto deletion PRs, giving label-aware automation a signal it can honour before ever attempting to arm.Labels
do-not-mergeanddeletions-acknowledgeddid not exist in this repo (gh pr edit --add-label do-not-mergefailed with'do-not-merge' not found). Both have been created, and the workflow re-creates them if missing so it stays drop-in for new repos.Verification
The detection logic was dry-run against real PRs before this workflow was written. On #4709 it reports:
and on #4692/#4710/#4711 it correctly identifies all three as held with auto-merge currently armed. No false positives were found: every open PR in this repo is armed, and the only ones flagged are those the reviewer had disarmed.
YAML parses, all three
run:blocks passbash -n, and the file satisfies this repo's ownlint-workflow-files.ymlrules (concurrency,cancel-in-progress: true, per-jobtimeout-minutes, no hosted runners, no protected-branch push).Companion change
Repository_Managementneeds the arming side fixed too, so agents stop attempting the arm in the first place rather than relying on the guard to undo it.