Accept MR API (PUT /merge) bypasses merge trains when pipeline already succeeded
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
- Have a project with merge trains enabled and pipelines must succeed checked
- Create an MR, let the pipeline pass (status:
success) - Approve the MR
- Call the Accept MR API:
PUT /projects/:id/merge_requests/:iid/merge?auto_merge=true&squash=true - 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
# ...
endOn 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)).