The concept of the detached pipeline within the GitLab Continuous Integration and Continuous Deployment (CI/CD) ecosystem represents a specialized execution state where a pipeline is triggered specifically by a merge request event rather than a standard branch push. In a standard GitLab workflow, a push to a branch typically triggers a branch pipeline. However, when a merge request (MR) is open, GitLab can generate a "detached" pipeline that exists in the context of the merge request itself. This mechanism is designed to provide a dedicated environment for testing changes specifically associated with the proposed merge, ensuring that the source branch's integrity is verified before it is integrated into the target branch.
The utility of these pipelines is rooted in the CI_PIPELINE_SOURCE variable. For a pipeline to be recognized as a merge request pipeline, the .gitlab-ci.yml configuration must explicitly include job rules or workflow rules that match the condition CI_PIPELINE_SOURCE == "merge_request_event". This distinction allows developers to define different sets of jobs for standard branch pushes versus those intended for the merge request lifecycle. When correctly configured, these pipelines run on the contents of the source branch only, ignoring the target branch's content, and are clearly identified by a "merge request" label in the pipeline lists. This separation is critical for maintaining a clean history and ensuring that the CI environment mirrors the exact state of the proposed changes.
Despite the intentional design, the interaction between rules:if statements and the generation of these pipelines has introduced significant technical challenges and behavioral anomalies. Specifically, the presence of certain rule configurations can lead to "pipeline duplication," where GitLab triggers both a regular branch pipeline and a detached merge request pipeline simultaneously for a single push. This redundancy not only consumes additional runner resources but also introduces complexities in dependency management and job execution, as the detached pipeline may not mirror the full set of jobs defined in the configuration, leading to catastrophic failures in complex pipelines.
Structural Requirements and Trigger Mechanisms for Merge Request Pipelines
The deployment of a merge request pipeline is not automatic for every project; it requires a specific set of prerequisites and configuration parameters to be met within the GitLab environment.
To successfully implement merge request pipelines, the following criteria must be satisfied:
- The project's
.gitlab-ci.ymlfile must contain job rules or workflow rules specifically targeting theCI_PIPELINE_SOURCE == "merge_request_event"condition. - The user triggering the pipeline must possess a specific role within the source project, namely Developer, Maintainer, or Owner.
- The repository must be hosted directly as a GitLab repository; external repositories do not support this specific pipeline functionality.
The triggers for these pipelines are multifaceted. A detached merge request pipeline is initiated when a user creates a new merge request from a source branch containing one or more commits. Similarly, any subsequent push of a new commit to that source branch while the merge request is active will trigger a new detached pipeline. Furthermore, users can manually initiate these pipelines by navigating to the "Pipelines" tab within the merge request interface and selecting "Run pipeline."
The impact of these triggers is that the pipeline focus shifts entirely to the source branch. Unlike merged results pipelines, which test the hypothetical result of merging the source and target branches, the detached pipeline focuses solely on the source branch's content. This provides an isolated testing environment that validates the developer's current work without the noise of the target branch's current state.
The Detached Pipeline Duality and the Rules Bug
A critical failure point has been identified in the way GitLab handles the coexistence of jobs with and without rules: blocks. When a .gitlab-ci.yml file contains a mixture of jobs—some utilizing rules:if and others relying on default behavior (no rules)—GitLab may trigger two separate pipelines: one regular branch pipeline and one detached merge request pipeline.
This phenomenon occurs particularly after a force push to a merge request. The "detached" version of the pipeline often becomes "mutilated," meaning it only includes jobs that have explicit rules: blocks defined. Any job that does not have a rules: block is omitted from the detached pipeline.
This creates a catastrophic failure in pipelines with strict dependencies. For example, consider a scenario where a deploy job is configured with rules:if to allow for manual triggers (when: manual) or scheduled nightly runs (when: on_success). This deploy job naturally depends on a build job. If the build job is a standard job without any rules: block, it will be missing from the detached pipeline. Consequently, the deploy job in the detached pipeline will fail because its required dependency (build) does not exist in that specific pipeline context.
The technical consequence of this behavior is that rules:if essentially forces the pipeline into a state where only "ruled" jobs are recognized in the detached context. This forces developers into two suboptimal choices: either duplicating every single job by adding redundant rules: blocks to every entry—which leads to configuration bloat—or reverting to the legacy only/except syntax to avoid the duplication bug.
Analysis of Pipeline Types and Deployment Variations
The complexity of GitLab pipelines is further illustrated by the variety of pipeline types used in large-scale environments, such as those seen in the GitLab Omnibus development process. These pipelines are categorized by their intent and the specific triggers that activate them.
| Pipeline Identifier | Target/Context | Characteristic/Behavior |
|---|---|---|
| GITLABMRPIPELINE | Canonical, Security | Triggered on MRs from specific branches; includes trigger jobs. |
| AUTODEPLOYBUILD_PIPELINE | Release | Triggered by auto-deploy tags. |
| CEBRANCHBUILD_PIPELINE | Release | Triggered by regular branch pushes (Community Edition). |
| CENIGHTLYBUILD_PIPELINE | Release | Scheduled nightly builds for CE. |
| CERCBUILD_PIPELINE | Release | Triggered by CE Release Candidate tags. |
| CETAGBUILD_PIPELINE | Release | Triggered by stable CE tags. |
| EEBRANCHBUILD_PIPELINE | Release | Regular branch pushes forced as Enterprise Edition. |
| EENIGHTLYBUILD_PIPELINE | Release | Nightly EE builds requiring forced EE status. |
| EERCBUILD_PIPELINE | Release | Triggered by EE RC tags. |
| EETAGBUILD_PIPELINE | Release | Triggered by stable EE tags. |
| TRIGGERCACHEUPDATE_PIPELINE | QA | Updates build cache; requires CACHE_UPDATE set to true. |
| TRIGGEREDCEPIPELINE | QA | Triggered e2e:test-on-omnibus for CE packages. |
| TRIGGEREDEEPIPELINE | QA | Triggered e2e:test-on-omnibus for EE packages. |
| FORKBRANCHTEST_PIPELINE | Forks | Test suite for fork projects; excludes trigger jobs and danger-review. |
| FORKMRPIPELINE | Forks | Specifically for MRs originating from forks. |
This structured diversity shows that GitLab's pipeline logic is not monolithic. The "detached" nature of the FORK_MR_PIPELINE and GITLAB_MR_PIPELINE allows the system to filter out unwanted jobs, such as danger-review, which may not be appropriate for all contexts, while still ensuring that critical trigger jobs are executed.
Advanced Job Configurations and Trigger Dependencies
Within these pipelines, jobs are often linked through a chain of triggers and dependencies. The interaction between these jobs becomes highly volatile when detached pipelines are improperly formed.
In high-complexity environments, specific jobs are designed to trigger subsequent processes in separate mirrors or projects. For instance:
Trigger:package: This job builds a single package as an artifact and is restricted to the QA mirror on triggered pipelines.Trigger:gitlab-docker: This job depends on the output ofTrigger:packageto build a GitLab Docker image.qa-subset-test: This is an automatically triggered job that activates when eitherTrigger:CE-packageorTrigger:EE-packageare manually triggered. It passes the Docker images to the GitLab QA Mirror for testing.qa-remaining-test-manual: A manual trigger job used to execute the remainder of the test suite. To run these in an MR pipeline, the user must manually trigger the package jobs first.
The dependency chain here (Package -> Docker Image -> QA Test) demonstrates why the "mutilation" of detached pipelines is so destructive. If the Trigger:package job lacked a rules: block, it would be stripped from the detached pipeline, rendering the Trigger:gitlab-docker and qa-subset-test jobs impossible to execute, regardless of whether they have rules defined.
Environmental Management and Review App Integration
The use of rules in detached pipelines also impacts the management of environments and review apps. A common failure pattern involves pipelines becoming "blocked" when using rules instead of the when keyword in the context of environment-specific jobs.
In a typical development workflow, jobs such as review-docs-deploy are used to trigger a documentation build in a separate project (gitlab-docs), which then deploys a review app based on the current commit. This is typically a manual job limited to branch pipelines. To maintain the lifecycle of these environments, a corresponding review-docs-cleanup job is required. This cleanup job is designed to run automatically upon the merging of a merge request to stop the environment and free resources.
When rules are improperly implemented in these scenarios, the pipeline may fail to transition states, leaving the review app in a "blocked" or "stuck" state. This is often because the rules logic prevents the cleanup job from being recognized as a valid part of the pipeline's final state, or conversely, prevents the deploy job from ever reaching the manual state required for user interaction.
Technical Mitigation and Strategic Workarounds
Given the current behavior of detached pipelines and the bugs associated with rules:if, developers must employ specific strategies to ensure pipeline stability.
The primary workaround to prevent the "mutilation" of detached pipelines is the "Rules Saturation" method. Since GitLab only includes jobs with rules: blocks in the detached pipeline, the developer must add a rules: block to every single job in the .gitlab-ci.yml file, even if those jobs are intended to run on every pipeline.
For a job that should always run, a rule such as:
yaml
job_name:
rules:
- when: always
must be explicitly added. This prevents the job from being stripped during the creation of the detached merge request pipeline.
Another alternative is the return to only and except keywords. While rules are more powerful and flexible, the only: [merge_requests] syntax avoids the duality bug where two pipelines are triggered simultaneously. By using only/except, the developer can ensure that only one pipeline is created, although they lose the ability to use complex if statements based on environment variables.
Furthermore, for those utilizing include:component or other included files, it is important to note that rules defined within an include: block do not satisfy the requirement for triggering a merge request pipeline. The CI_PIPELINE_SOURCE == "merge_request_event" rule must be present in the main .gitlab-ci.yml or a workflow rule must be defined at the top level to correctly categorize the pipeline as "detached."
Conclusion: Analysis of the Detached State Impact
The detached pipeline is a powerful feature designed to isolate merge request validation from general branch activity. However, the current implementation of the rules: engine creates a paradoxical state where the presence of sophisticated logic (rules:if) triggers a failure in basic job persistence. The "mutilation" of the detached pipeline—where jobs without rules are discarded—fundamentally breaks the contract of CI/CD dependencies.
The fact that a force push can trigger both a branch pipeline and a detached pipeline indicates a synchronization failure in how GitLab identifies the pipeline's intent. This duality results in resource waste and "pipeline noise," where developers must sift through two sets of results for a single commit. The impact on the developer experience is significant: instead of focusing on code quality, engineers are forced to spend time "spamming" their configuration files with rules: when: always just to ensure their build jobs are not deleted by the system.
Until the logic governing the intersection of rules: and detached pipelines is unified, the most stable configuration for complex, dependency-heavy projects remains the use of explicit rules: on every job or a strategic move back to only/except for merge request contexts. The detached pipeline's failure to maintain the full job graph is a critical bottleneck in achieving a seamless "push-to-test" workflow in GitLab.