Fix auto-merge race condition for branch pipelines

Summary

Fixes a race condition where merge requests with auto-merge enabled via push options merge immediately before the pipeline is created, on projects using branch pipelines (no merge request pipeline rules in their CI config).

Behind the track_ref_pipeline_creation feature flag.

Root cause

When auto-merge is set via push options (-o merge_request.merge_when_pipeline_succeeds), AutoMergeProcessWorker can run before the branch pipeline is created by CreatePipelineWorker. During this window:

  1. MergeRequest#has_ci_enabled? returns false (no head_pipeline_id, no all_pipelines, pipeline_creating? is false)
  2. CheckCiStatusService#pipeline_must_succeed? returns false (because has_ci_enabled? is false)
  3. CheckCiStatusService returns success — CI check passes vacuously
  4. MergeWhenChecksPassService#process finds the MR mergeable? and merges immediately

The race exists because branch pipeline creation uses Ci::PipelineCreation::Requests.start_for_project (project-level Redis flag), but MergeRequest#pipeline_creating? only checks pipeline_creating_for_merge_request? (MR-level flag). The branch pipeline creation signal is invisible to the MR.

Fix

Add ref-level pipeline creation tracking to Ci::PipelineCreation::Requests:

  • start_for_ref(project, ref) — sets a Redis flag when a pipeline is being created for a ref
  • pipeline_creating_for_ref?(project, ref) — checks if a pipeline is being created for a ref
  • complete_for_ref(project, ref, status:) — resolves all ref-level requests on pipeline creation success or failure
  • MergeRequest#pipeline_creating? now also checks pipeline_creating_for_ref? for its source branch

start_for_ref is called synchronously in PushOptionsHandlerService when auto-merge is requested via push options. This fully eliminates the race window — the flag is set before any async worker can run. It is also called in Ci::CreatePipelineService#execute_async for defence in depth.

Ref-level requests are resolved in Ci::CreatePipelineService#execute on both success and failure paths, so projects without CI are not permanently blocked (the flag clears when pipeline creation fails with "Missing CI config file").

Feature flag

  • Name: track_ref_pipeline_creation
  • Type: gitlab_com_derisk
  • Rollout issue: #597220

Reproduction

Reproduced on GDK with:

  • Branch pipeline CI config (no rules: - if: $CI_MERGE_REQUEST_IID)
  • only_allow_merge_if_pipeline_succeeds: false
  • async_mr_pipeline_creation: false (default on 18.9.x)
  • Artificial 30s sleep in CreatePipelineWorker to widen the race window
  • Push with -o merge_request.merge_when_pipeline_succeeds

MR merged immediately with zero pipelines. With the fix applied, pipeline_creating? returns true during the race window, blocking the premature merge.

References

Edited by Marc Shaw

Merge request reports

Loading
Loading