Fix race in auto-assign code-owner reviewers on MR create

What does this MR do and why?

The auto-assign code-owner reviewers chain enqueued from EE::MergeRequests::CreateService#after_create raced against the merge head diff being built.

The first MergeRequests::SyncCodeOwnerApprovalRulesWorker pass on a fresh MR ran before the diff was finalized, so Gitlab::CodeOwners.entries_for_merge_request returned an empty array, no code-owner approval rules were persisted, but AutoAssignReviewersWorker was still enqueued unconditionally. When that worker ran, CodeOwnersStrategy#select_reviewers had no code_owner? rules to read, so AssignService returned skipped('No eligible reviewers')ServiceResponse.success, no log line, no reviewer assigned. A later inline sync (from ReloadMergeHeadDiffService) populated the rules ~1s later but didn't re-fire the chain.

Reproduced locally in GDK and on playground project MR gitlab-com/create-stage/code-review-ai-experiment-playground!80 (closed). Sample timeline on local MR 606:

Event Time
MR created 09:10:34.917
AutoAssignReviewersWorker start 09:10:35.289
AutoAssignReviewersWorker done (silent skip) 09:10:35.329
Code-owner approval rule persisted 09:10:36.161

The draft -> ready path in EE::MergeRequests::UpdateService is not affected — that path enqueues AutoAssignReviewersWorker directly long after rules are synced. Verified working in the playground project: gitlab-com/create-stage/code-review-ai-experiment-playground!81 (closed).

Fix

Replace the in-band enqueue_auto_reviewer_assignment worker arg with a one-shot Redis flag (MergeRequests::ReviewerAssignment::PendingInitialAssignment, TTL 10 min):

  1. EE::MergeRequests::CreateService#after_create calls PendingInitialAssignment.mark(issuable) if the project has the feature enabled. The worker is enqueued without the old param.
  2. EE::MergeRequests::ReloadMergeHeadDiffService consumes the pending flag and enqueues AutoAssignReviewersWorker once — after MergeabilityCheckService has rebuilt the merge head diff and the EE override has re-synced code-owner approval rules against it. The consume is atomic (DEL == 1), so subsequent reload paths (push refresh, recheck) see no flag and don't re-fire.

Also: the sidekiq job hash uses string keys now to silence the recurring strict_args warning (expire_unapproved_key is a Symbol).

How to set up and validate locally

  1. In Rails console, enable the FF and confirm Ultimate license: Feature.enable(:auto_assign_code_owner_reviewers)
  2. Pick a project with code_owners licensed feature, add CODEOWNERS (e.g. * @some-user), and project.project_setting.update!(reviewer_assignment_strategy: :code_owners).
  3. Open an MR via the API or UI as a different user than the code owner.
  4. Observe in log/sidekiq.log the chain SyncCodeOwner done -> AutoAssign start -> done and the code owner assigned as reviewer.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Marc Shaw

Merge request reports

Loading