Prevent bypassing the merge trains
## Problem to Solve Merge Trains were designed as an operational control to keep the target branch green by ensuring every MR is validated against the actual post-merge state of the branch before it lands. However, two escape hatches undermine this guarantee: 1. **The UI exposes a "Merge immediately" option** in the merge widget dropdown, which bypasses the train entirely, merges directly to the target branch, and invalidates all currently running merge train pipelines — forcing every in-flight MR to rebuild. "Merge immediately" is available to anyone who can merge to the target branch (not only maintainers or owners) 2. **The `/merge` REST API endpoint** (`PUT /projects/:id/merge_requests/:iid/merge`) performs an immediate merge by default, with no awareness of the merge train. A caller using the API (automation, bots (e.g. Renovate), scripts(CLI)) can bypass the train silently. This API endpoint can be called by anyone who can merge to the target branch. For teams that rely on merge trains as a correctness guarantee, there is currently no way to enforce that **all** merges go through the train. The existing `merge_trains_skip_train_allowed` setting (introduced in #331415) is a bit of the inverse and controls whether skipping is allowed in the UI, but the API remains unaffected. ## Proposal Introduce a new project-level setting in **Settings \> Merge Requests**, nested under the existing "Merge Train Enforcement" section ([recommendation](https://gitlab.com/gitlab-org/gitlab/-/work_items/597962#note_3300591207)): > **Merge train enforcement** > > ○ **Allow bypass** (default) — Users with permission to merge can bypass the merge train through the UI or API. > > ○ **Enforce for all users** — All merge requests must go through the merge train. You cannot bypass the merge train through the UI or API. To merge through the API, use [auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/) instead. > > ○ **Enforce with Owner override** — All merge requests must go through the merge train. Owners and administrators can bypass the merge train for individual merge requests through the UI or API. With this option, we get: * **Backward compatibility** — "Allow bypass" is the default, so existing projects are unaffected. * **Positive-action semantics** — you're selecting what you want, not clearing something to prevent something else. * **Easier to document** — "From the **Merge train enforcement** options, select Enforce for all users" reads naturally. * **Extensibility** — the third option maps directly to the future iteration for emergency override described in this issue, without needing a second checkbox or separate mechanism. ### Behaviour when enabled #### UI changes - The "Merge immediately" dropdown option is hidden from the merge widget entirely (not just greyed out). - No confirmation dialog is shown, because the option does not exist. #### REST API changes `PUT /projects/:id/merge_requests/:iid/merge` — when this setting is enabled, the endpoint behaves as follows: **Default behavior (no `auto_merge` parameter, or `auto_merge=false`):** The request is **rejected** with a `405 Method Not Allowed` response and a clear error message indicating that immediate merges are not permitted on this project and that the caller must use `auto_merge=true` to add the MR to the merge train. **With `auto_merge=true`:** The MR is queued to be added to the merge train once all checks have passed. This is the equivalent of "Set to auto-merge via merge train" in the UI. If the MR is already on the train: a `200` response is returned indicating the MR's current position on the train The `skip_merge_train` parameter (used internally) is ignored/treated as `false` when this setting is active. **Note on Renovate and automation tools:** Tools calling `/merge` without `auto_merge=true` will receive a `405` rejection when this setting is enabled. To resolve this, `platformAutomerge` should be enabled in Renovate's configuration, which causes Renovate to pass `auto_merge=true` and correctly queue the MR via the merge train. #### GraphQL Changes TBD #### Emergency Override To avoid the setting being too blunt for production use, a temporary override mechanism should be available: **First iteration:** A project Owner or Admin can temporarily disable the setting at the project level to perform an emergency merge, then re-enable it. This is a manual process but provides an escape hatch without requiring a separate bypass mechanism. **Future iteration:** A dedicated "emergency merge" action available to Owners/Admins that bypasses the train for a single MR with a required reason field, logged in the audit trail. ## Migration & Rollout Considerations * The setting is **opt-in** and disabled by default. Existing projects are unaffected unless an admin explicitly enables it. * **Renovate users** will need to enable `platformAutomerge: true` in their Renovate configuration once this setting is enabled. This causes Renovate to pass `auto_merge=true`, which correctly queues MRs via the merge train (delivered via `#593465`). * **Other automation tools** calling `/merge` without `auto_merge=true` will need to be updated to pass `auto_merge=true`. The `405` rejection provides a clear and actionable error message to guide this. ## Related issues - [#9186 (closed)](https://gitlab.com/gitlab-org/gitlab/-/work_items/9186) — Merge Trains MVC (original design, includes "Merge immediately" as a first-class escape hatch) - #331415 — Backend: add toggle to disable skip merge train (the existing `merge_trains_skip_train_allowed` setting, UI-only) - [#331357](https://gitlab.com/gitlab-org/gitlab/-/work_items/331357) — UX: change "Merge immediately" button text when merge trains are enabled - [#199096](https://gitlab.com/gitlab-org/gitlab/-/work_items/199096) — Control who can Merge Immediately when merge train is enabled - [#350553](https://gitlab.com/gitlab-org/gitlab/-/work_items/350553) — Clearly communicate the impact of `Merge immediately` with merge trains enabled - [#32665](https://gitlab.com/gitlab-org/gitlab/-/work_items/32665) — API support for merge trains - [#593465](https://gitlab.com/gitlab-org/gitlab/-/work_items/593465) — Accept MR API bypasses merge trains when pipeline already succeeded ## Success Criteria A project with this setting enabled has zero successful direct merges through any path (UI, REST, GraphQL, automation) --- ## Historical Context: A Pattern of Mixed Signals The design intent of Merge Trains has never been clearly settled, and a trail of issues spanning 2019 to 2026 reveals a consistent pattern of decisions that pull in opposite directions — sometimes treating the train as an advisory workflow, sometimes as a hard gate, and sometimes accidentally breaking both. ### 2019: "Merge immediately" was a deliberate first-class escape hatch The original MVC issue [#9186 (closed)](https://gitlab.com/gitlab-org/gitlab/-/work_items/9186) explicitly included "Merge immediately" as a dropdown option alongside "Start merge train" in every UX scenario. The rationale was pragmatic: > "We still allow a merge when the pipeline is red (failed) because there might be situations where you **have to** merge." This was a conscious product decision — the train was designed as a strong default, not an enforced gate. The escape hatch was intentional. ### 2022: Users confused by the impact of "Merge immediately" Issue [#350553](https://gitlab.com/gitlab-org/gitlab/-/work_items/350553) was filed because users didn't understand that clicking "Merge immediately" would silently **invalidate all currently running merge train pipelines**, forcing every in-flight MR to rebuild from scratch. The confirmation modal existed but didn't communicate this clearly. The proposal was to improve the modal text — treating the escape hatch as a UX problem to clarify, not a feature to remove. This issue remains open and unresolved as of 2026. ### 2023: A well-intentioned fix creates a new breakage Issue [#428197 (closed)](https://gitlab.com/gitlab-org/gitlab/-/work_items/428197) removed the requirement for users to manually rebase before adding to a fast-forward merge train, since the train can auto-rebase internally. This was a genuine improvement for the train workflow. However, it had an unintended side effect: by changing `should_be_rebased?` to return `false` when fast-forward merge trains are enabled, the "Rebase" button was hidden from the UI. A user noted the consequence directly in the issue comments: > "It seems to not include the rebase (like in the merge train), and we have an error if the source branch is behind the target branch. It's currently a problem for hotfixes... I know it's not related to this change directly, it's just that we had a workflow for hotfixes, with the `rebase without pipeline` button before, and it's now unavailable for our developers." — [@florian.dhaussy1](https://gitlab.com/florian.dhaussy1), [#428197 note](https://gitlab.com/gitlab-org/gitlab/-/issues/428197#note_1679362367) ### 2023: The workaround is to hide the button Rather than solving the underlying problem, issue [#429010 (closed)](https://gitlab.com/gitlab-org/gitlab/-/work_items/429010) was created to **disable the "Merge immediately" UI** for fast-forward and semi-linear merge trains entirely, because a skip-merge in those modes causes the train ref to diverge from the target branch in a way that can't be fast-forward merged. The fix was to hide the button and document the limitation. The companion issue [#429009](https://gitlab.com/gitlab-org/gitlab/-/work_items/429009) — to properly fix the underlying problem by rebasing the train ref after a skip-merge — remains **open and unresolved** as of 2026, blocked by other work. ### 2023: The button is hidden but the API still works Issue [#434070](https://gitlab.com/gitlab-org/gitlab/-/work_items/434070) captures the resulting state: the "Merge immediately" button is now hidden in the UI for fast-forward projects when the source branch is behind, but the `/merge` API endpoint has **no such guard**. A caller using the API will receive a `405 Method Not Allowed` error with no explanation of why, and no guidance on what to do instead. The workaround documented in the issue is to rebase manually via the API or locally. ### 2026: The API bypass is confirmed as a real-world problem Issue [#593465](https://gitlab.com/gitlab-org/gitlab/-/work_items/593465) documents that the `/merge` API endpoint silently bypasses merge trains when `auto_merge=true` is passed and the pipeline has already succeeded. This was discovered by a GitLab team member using `glab mr merge --auto-merge` on a merge-train project and observing that the MR merged immediately without going through the train. The discussion in that issue is itself a microcosm of the broader design ambiguity. GitLab engineers debated whether this was a bug or expected behaviour: > "@marc_shaw I think we've discussed this before, so apologies in advance... but why doesn't `/merge` know about merge trains? Pushing the requirement to know about that to the consumer seems odd..." — [@phikai](https://gitlab.com/phikai) > "That endpoint does not support merge train. It needs to use the merge_train endpoint." — [@marc_shaw](https://gitlab.com/marc_shaw) > "There will be users relying on as it currently is... I think we could introduce a new endpoint or maybe an attribute on the `/merge` so that the users have to explicitly decide to use the merge train branch." — [@marc_shaw](https://gitlab.com/marc_shaw) > "I'm not suggesting we couldn't do more here to potentially make the non-merge train API more aware... but I'm also not really sure this is a bug (in the sense of the application doing something it's not supposed to do)." — [@phikai](https://gitlab.com/phikai) The issue was ultimately relabelled from `type::bug` to `type::feature`, reflecting the conclusion that the API is working as designed — it just wasn't designed with merge trains in mind. A GitLab Ultimate self-managed customer was confirmed to be actively impacted, with Renovate Bot silently bypassing their merge train on every dependency update. Their merge train regularly runs 20+ deep, meaning every bypass triggers a full restart of all in-flight pipelines — hundreds of extra builds per day. ### The "Allow skipping the merge train" checkbox adds to the confusion The existing `merge_trains_skip_train_allowed` setting — surfaced in the UI as "Allow skipping the merge train" — was intended to give teams control over whether the "Merge immediately" escape hatch is available. However, as noted in [#593465](https://gitlab.com/gitlab-org/gitlab/-/work_items/593465): > "The `Allow skipping the merge train` setting is confusing at best and misleading at worst. I don't think it matters what that feature actually is intending to do — it is implicitly stating that unless this checkbox is checked, Merge Train 'skipping' is not allowed." — [@poffey21](https://gitlab.com/poffey21) In practice, the setting only affects the UI. The API bypass exists regardless of how this checkbox is configured. ### Summary Across seven years and more than a dozen related issues, GitLab has: - Intentionally designed "Merge immediately" as an escape hatch (2019) - Been unclear about its destructive impact to users (2022, still open) - Accidentally broken the "Merge immediately" flow for fast-forward projects while fixing an unrelated issue (2023) - Hidden the button as a workaround rather than fixing the root cause (2023, root cause still open) - Left the `/merge` API completely unaware of merge trains, allowing silent bypasses (confirmed 2026) - Debated internally whether the API bypass is a bug or a feature (2026, resolved as "feature") - Added a settings checkbox that implies enforcement but doesn't actually enforce anything at the API level This issue proposes resolving that ambiguity with a clear, explicit opt-in: teams that want merge trains to be a true operational control can enable a setting that enforces it consistently across the UI, the REST API, and automation tooling.
issue