Accept MR API (PUT /merge) bypasses merge trains when pipeline already succeeded

🤖 AI-generated

Summary

The "Merge a merge request" REST API (PUT /projects/:id/merge_requests/:iid/merge) allows bypassing merge trains on projects that have merge trains enabled. When auto_merge=true is passed and the MR pipeline has already succeeded, the API merges the MR directly into the target branch without adding it to the merge train. No merge-train pipeline runs to verify compatibility with the current HEAD.

This effectively circumvents the merge train safety mechanism that projects rely on to ensure sequential verification of changes.

Steps to reproduce

  1. Have a project with merge trains enabled and pipelines must succeed checked
  2. Create an MR, let the pipeline pass (status: success)
  3. Approve the MR
  4. Call the Accept MR API:
    PUT /projects/:id/merge_requests/:iid/merge?auto_merge=true&squash=true
  5. The MR is merged immediately and directly — no merge-train pipeline is created

Root cause

In lib/api/merge_requests.rb, the execute_merge_with_fix helper (behind the fix_merge_api_mergeability_check feature flag) has this logic:

def execute_merge_with_fix(merge_request, auto_merge)
  if auto_merge
    strategy_available = AutoMergeService.new(...)
      .available_strategies(merge_request)
      .include?(merge_request.default_auto_merge_strategy)

    if strategy_available
      AutoMergeService.new(...).execute(merge_request, ...)
    elsif merge_request.diff_head_pipeline_success?
      # <-- THIS PATH: direct merge, no merge train check!
      MergeRequests::MergeService.new(...).execute(merge_request)
    else
      not_allowed!
    end
  # ...
end

On merge-train projects, default_auto_merge_strategy returns merge_when_checks_pass, but the available strategies are the merge-train strategies (merge_train, add_to_merge_train_when_checks_pass). Since the default strategy is not in the available list, the code falls through to the elsif branch which calls MergeService directly — bypassing the merge train entirely.

The legacy code path (execute_merge_legacy) has the same issue: immediately_mergeable_legacy returns true when auto_merge && pipeline_success, leading to a direct MergeService.execute call.

Expected behavior

When merge trains are enabled on a project, the Accept MR API with auto_merge=true should either:

  • Add the MR to the merge train (delegating to the merge train strategy), or
  • Return an error (e.g. 405) indicating that the merge train API should be used instead

It should not merge directly, as this defeats the purpose of merge trains.

Impact

Any API client (CI bot, CLI tool, automation) calling PUT /merge_requests/:iid/merge with auto_merge=true on a merge-train project will silently bypass the merge train when the pipeline is green. The user gets a "Merged!" response with no indication that the merge train was skipped. This can introduce broken commits to the target branch — exactly what merge trains are designed to prevent.

Workaround

Use the Merge Train API (POST /projects/:id/merge_trains/merge_requests/:iid) instead of the Accept MR API. Note: passing auto_merge=true to the merge train API also has a separate bug when checks already passed (tracked in #592889 (closed)).

Edited by 🤖 GitLab Bot 🤖