fix(merge-queue): HOLD on persistent 409-conflict-on-update (HOL guard) (#2352) #2354
Reference in New Issue
Block a user
Delete Branch "fix/merge-queue-hold-on-409-conflict-update"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fixes #2352 — the autonomous merge-queue (
.gitea/scripts/gitea-merge-queue.py) head-of-line-blocked on a queued PR whose branch-update hit a persistent HTTP 409 merge-conflict. The conflicted PR sat at the queue head and was retried every*/5tick, never advancing to other ready PRs. ~25 stale conflicted PRs clogged the queue this way (observed PR #1409).Fix
Treat a persistent 409-conflict-on-update as a HOLD condition, parallel to the permission-error path #2349 added for
MergePermissionError:BranchUpdateConflictError(subclass ofApiError).update_pullre-raises on an explicit-> HTTP 409status token."409"substring — the PR number/path can itself contain409(e.g./pulls/1409/update) and must not be misread as a conflict. (This case actually surfaced while writing the regression test for the exact PR in the issue.)process_once'supdatebranch catches it, appliesHOLD_LABEL, and advances to the next queued PR (choose_next_queued_issueskips held PRs).mergeable=None(Gitea still computing conflict state), which remains a transient WAIT with no hold (the fix #2349 already added).hold_pr()helper; reused it in the merge-permission path (no behavior change there).Fail-closed: a held PR is skipped, never merged; it stays open with
merge-queue-holdfor a human to rebase, then remove the label to requeue.Tests (per §SOP-22)
5 new tests; the existing 26 stay green (31 total in this module; full
.gitea/scripts/tests= 262 passed, 2 pre-existing skips):test_process_once_holds_pr_on_409_conflict_on_update— 409-on-update → PR held, not merged.test_queue_advances_past_held_conflicted_pr— end-to-end HOL proof: after the hold,choose_next_queued_issueselects the next ready PR (#1500), not the held #1409.test_update_pull_raises_conflict_error_on_409/test_update_pull_reraises_non_409_errors— 409 → conflict subclass; 500 → plainApiError(not swallowed).test_BranchUpdateConflictError_inherits_from_ApiError.Run via the
Ops Scripts Testsjob (python -m pytest .gitea/scripts/tests -q).Routed to
agent-reviewer-cr2+agent-researcher. Do not self-merge.🤖 Generated with Claude Code
APPROVED on current head
7fb66f473d.5-axis: correctness OK: explicit /update HTTP 409 is converted to BranchUpdateConflictError, process_once applies merge-queue-hold and returns without merging, so persistent conflicted PRs no longer HOL-block the queue. Robustness OK: matching is on the status token
-> HTTP 409, not incidental PR/path digits; non-409 API errors still propagate instead of being misclassified; mergeable=None remains a transient wait. Security OK: no approval/required-CI/merge gating is loosened; this path only prevents merging a conflicted PR and advances after holding it. Performance OK: no new expensive polling/loops. Readability OK: named exception + hold_pr helper make the permanent-conflict path clear.Tests cover 409 classification, non-409 re-raise, hold-not-merge on conflict, and selecting the next queued PR after the hold. Latest required CI is green via CI/all-required plus Ops Scripts Tests; mergeable=true. Raw sop/security ceremony failures are outside the required merge gate here.
Approved on current head
7fb66f47. Independent review confirmed 409-on-update HOL self-heal is fail-closed: only the structured status token "-> HTTP 409" becomes BranchUpdateConflictError, non-409 errors rethrow, process_once applies HOLD_LABEL and does not merge, and held PRs are skipped so the queue advances. Required CI/Ops Scripts are green; mergeable=true.merge-queue: updated this branch with
mainat4b56cabe24f3. Waiting for CI on the refreshed head.APPROVED on current head
e5daf96dab.Re-reviewed after the branch update. The diff remains scoped to the 409 branch-update HOL self-heal in .gitea/scripts/gitea-merge-queue.py plus regression tests. The 409 handling still matches the previously approved behavior: it detects explicit "-> HTTP 409", holds the conflicted PR with merge-queue-hold, and does not merge or retry indefinitely. Required contexts visible for the core merge bar are green (CI / all-required, E2E API Smoke Test, Handlers Postgres Integration), PR is mergeable=true, and the merge-queue label is present. Non-required SOP checklist/tier red statuses are not part of the pre-#2331 required bar.
Approved on current head
e5daf96d. Reconfirmed required contexts are green, mergeable=true, and the head move is merge-from-main with the 409-HOL self-heal unchanged.