Auto-merge fails for concurrent MRs: mark_as_unchecked breaks merge flow for sibling MRs
### Summary Auto-merge (`merge_when_checks_pass`) fails for concurrent MRs targeting the same branch. When multiple MRs have auto-merge enabled and their pipelines succeed within a short window, a chain reaction of `mark_as_unchecked` calls prevents ~60% of MRs from reaching `can_be_merged` state, leaving them stuck with no timeout or recovery mechanism. This is a follow-up to #592733. The fix in !217382 (callback on transition to `can_be_merged`) resolves the single-MR scenario but does not help when concurrent merges reset sibling MRs back to `unchecked`. ### Failure mechanism 1. Multiple MRs target the same branch with auto-merge enabled, pipelines succeed 2. `AutoMergeProcessWorker` picks up MR-A, merges it into the target branch 3. `mark_as_unchecked` resets `merge_status` for all other MRs targeting that branch (MR-B, MR-C, ...) 4. Workers for MR-B/MR-C see `mergeable?=false` (status is `unchecked`) and return without action 5. The `can_be_merged` callback from !217382 never fires because the transition from `unchecked` to `can_be_merged` does not happen without an external trigger 6. MR-B eventually gets rechecked and merges, but this triggers `mark_as_unchecked` again for MR-C and others 7. Cycle repeats: each merge unsticks 1-2 MRs but resets the rest No internal mechanism re-triggers mergeability checks after `mark_as_unchecked` for MRs with auto-merge enabled. ### Steps to reproduce 1. Create 30+ MRs targeting the same branch, each with a trivial one-line change 2. Enable auto-merge on all of them (via API or `glab mr merge --auto-merge`) 3. Wait for all pipelines to succeed 4. Observe: 12 out of 30 merge (40%); the rest stay stuck with `merge_status=unchecked` and `auto_merge_enabled=true` ### Workaround An external `GET /api/v4/projects/:id/merge_requests/:iid` call on a stuck MR triggers `check_mergeability` through the response serializer (via `detailed_merge_status`), forcing `unchecked` -> `checking` -> `can_be_merged` -> callback -> merge. This requires external polling. ### Test results (GitLab CE 18.9.3) | Approach | Batch | Merged | Stuck | Rate | | ------------------------------- | ----- | ------ | ----- | ---- | | Baseline (approve + auto-merge) | 30 | 12 | 18 | 40% | | + retry loop (30x20s) | 30 | 19 | 11 | 63% | | + poll GET + retry loop | 50 | 34 | 16 | 68% | | + after_script GET nudge | 50 | 39 | 11 | 78% | | + after_script list API recheck | 50 | 40 | 10 | 80% | ### Environment - GitLab CE 18.9.3, single-node (8 vCPU, 16 GB RAM) - PostgreSQL 16 (managed), Redis (local container) - Sidekiq: 4 processes x 20 concurrency ### Proposal When `mark_as_unchecked` resets `merge_status` for MRs that have `auto_merge_enabled=true`, it should also re-enqueue `AutoMergeProcessWorker` for those MRs (similar to the `can_be_merged` callback in !217382). This re-evaluates auto-merge MRs after their mergeability status is invalidated. ### What is the current _bug_ behavior? MRs with auto-merge enabled get stuck in `merge_status=unchecked` when concurrent merges trigger `mark_as_unchecked`. No timeout or recovery mechanism exists. ### What is the expected _correct_ behavior? All MRs with auto-merge enabled should eventually merge after their pipelines succeed, regardless of batch size (tested with 30-50 MRs targeting the same branch). ### Relevant logs and/or screenshots See test results table above. ### GitLab version 18.9.3-ee (CE features only)
issue