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:
MergeRequest#has_ci_enabled?returnsfalse(nohead_pipeline_id, noall_pipelines,pipeline_creating?is false)CheckCiStatusService#pipeline_must_succeed?returnsfalse(becausehas_ci_enabled?is false)CheckCiStatusServicereturnssuccess— CI check passes vacuouslyMergeWhenChecksPassService#processfinds the MRmergeable?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 refpipeline_creating_for_ref?(project, ref)— checks if a pipeline is being created for a refcomplete_for_ref(project, ref, status:)— resolves all ref-level requests on pipeline creation success or failureMergeRequest#pipeline_creating?now also checkspipeline_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: falseasync_mr_pipeline_creation: false(default on 18.9.x)- Artificial 30s sleep in
CreatePipelineWorkerto 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.