The implementation of conditional execution within GitLab CI/CD pipelines represents a critical juncture in the lifecycle of software delivery. At the heart of this control mechanism lies the when: never directive, a powerful tool used to exclude specific jobs or entire pipelines based on predefined rules. Understanding the nuance of when: never is not merely a matter of syntax but a requirement for preventing redundant execution, optimizing resource allocation, and ensuring that the pipeline behaves predictably across various trigger sources. When configured correctly, when: never allows a DevOps engineer to surgically remove jobs from a pipeline based on the context of the trigger, such as whether the event is a push, a merge request, a scheduled task, or an API call. However, the interaction between job-level rules and global workflow rules can lead to complex behaviors, including the dreaded "duplicate pipeline" scenario, where both a branch pipeline and a merge request pipeline are triggered simultaneously. This architectural complexity requires a deep understanding of how GitLab evaluates rules sequentially and how the CI_PIPELINE_SOURCE variable dictates the path of execution.
The Mechanics of When Never
The when: never keyword is the definitive instruction to the GitLab CI runner that a job must not be added to the pipeline under the conditions specified in the rules block. Unlike other when values such as on_success or always, when: never serves as a hard stop.
- Direct Fact: The
when: neverdirective prevents a job from being added to a pipeline. - Impact Layer: For the end user, this means specific resource-heavy tests or deployment scripts are skipped when they are not relevant to the current trigger, reducing wait times and compute costs.
- Contextual Layer: This directive operates within the
rulessyntax, which replaces the olderonly/exceptlogic. Because rules are evaluated in order, placing awhen: neverrule at the top of the list allows for the immediate exclusion of a job before other permissive rules are evaluated.
Pipeline Source Control via CIPIPELINESOURCE
The effectiveness of when: never is heavily dependent on the CI_PIPELINE_SOURCE predefined variable. This variable identifies the origin of the pipeline trigger, allowing the system to differentiate between a developer pushing code and an automated system triggering a build.
The following table outlines the critical values of CI_PIPELINE_SOURCE and their availability across different pipeline types:
| Variable Value | Description | Branch | Tag | Merge Request | Scheduled |
|---|---|---|---|---|---|
push |
Triggered by a git push | Yes | Yes | ||
schedule |
Triggered by a scheduled pipeline | Yes | Yes | ||
merge_request_event |
Triggered by MR creation/update | Yes | Yes | ||
api |
Triggered via the Pipelines API | Yes | Yes | Yes | Yes |
chat |
Triggered via ChatOps commands | Yes | Yes | Yes | Yes |
external |
Triggered by non-GitLab CI services | Yes | Yes | Yes | Yes |
external_pull_request_event |
Triggered by GitHub external PRs | Yes | Yes | Yes | Yes |
- Direct Fact:
CI_PIPELINE_SOURCEis used to control job inclusion based on the trigger event. - Impact Layer: Users can ensure that deployment jobs only run during
merge_request_eventand never during a standardpush, preventing accidental deployments to production from feature branches. - Contextual Layer: When combined with
when: never, this variable enables the creation of complex logical gates. For example, a job can be configured to run for all sources excepttriggerorschedule.
Preventing Duplicate Pipelines with Workflow Rules
A common failure point in GitLab CI configuration is the emergence of duplicate pipelines. This occurs when both a branch pipeline and a merge request pipeline are triggered by the same push event. This typically happens when jobs are defined with rules that satisfy both conditions without a global override.
- Direct Fact: Using
when: alwaysin job rules without correspondingworkflow: rulescan lead to pipeline warnings and duplicate runs. - Impact Layer: Duplicate pipelines waste runner minutes, clutter the UI, and can cause race conditions if the jobs involve modifying external infrastructure.
- Contextual Layer: To solve this, the
workflow: rulesblock must be used to define the global conditions under which a pipeline is created. This acts as a primary filter before individual job rules are even considered.
Example of a configuration that avoids duplicate pipelines by utilizing workflow: rules:
yaml
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
- when: never
In the example above, the pipeline is only created if the event is a merge request or if the commit is on the main branch; otherwise, it is never triggered.
The Interaction of Rules and Changes
The rules keyword allows for the inclusion of a changes clause, which monitors specific files or directories. When when: never is used in conjunction with changes, the logic becomes a conditional filter based on file modifications.
- Direct Fact: A job can be set to
when: neverif specific files are not changed, or it can be triggered only when specific files are modified. - Impact Layer: This allows for "monorepo" style optimizations where the
build_livejob only runs if changes occur infolder1/**/*orfolder2/**/*. - Contextual Layer: There is a known risk where a job might execute even when a
when: neverrule for a specific source (liketrigger) is present, if a previous rule in the list was already satisfied.
Consider a scenario where a job is triggered via the API. If the rules are configured as follows:
yaml
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
changes:
- "folder1/**/*"
- "folder2/**/*"
- if: '$CI_PIPELINE_SOURCE == "trigger"'
when: never
If a trigger is sent and there is a change in folder1 on the master branch, the first rule is satisfied. Because GitLab evaluates rules top-down and stops at the first match, the when: never for the trigger source is ignored, and the job executes. To fix this, the logic must be combined into a single rule:
yaml
rules:
- if: $CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE != "trigger"
changes:
- "folder1/**/*"
- "folder2/**/*"
when: on_success
- if: '$CI_PIPELINE_SOURCE == "trigger"'
when: never
Tag Pipelines and the CICOMMITTAG Paradox
Managing pipelines triggered by tags often involves the CI_COMMIT_TAG variable. Users attempting to suppress tag pipelines using when: never have reported instances where pipelines are still created despite these rules and the use of push options like ci.skip.
- Direct Fact: Some users have observed that
workflow: rulesspecifyingwhen: neverfor$CI_COMMIT_TAGare ignored in certain GitLab Community Edition versions (e.g., 13.12.0). - Impact Layer: This leads to unintended pipeline executions when tagging releases, which can trigger automatic deployment scripts that should have been suppressed.
- Contextual Layer: This behavior highlights the importance of verifying the GitLab Runner version and the specific behavior of the
workflowblock versus job-levelrules. In some cases, theci.skippush option is ignored if the internal GitLab logic determines the pipeline must be created based on the commit's metadata.
Integration with DevSecOps and CI/CD Components
Modern GitLab versions (specifically GitLab 16) have introduced CI/CD components to replace the repetitive use of the include keyword. These components allow for the standardization of rules and when: never logic across an entire organization.
- Direct Fact: GitLab 16 introduced CI/CD components as an experimental feature for reusable pipeline building.
- Impact Layer: Instead of every project manually defining
when: neverfor duplicate pipelines, a centralized "Standard Pipeline Component" can be published to a catalog, ensuring all projects follow the same trigger logic. - Contextual Layer: This is integrated with GitLab Duo's AI-powered workflows, which help developers configure these complex rules without having to manually map every
CI_PIPELINE_SOURCEvariable.
Logical Conflicts: Only/Except vs Rules
A critical technical restriction in GitLab CI is the prohibition of mixing only/except and rules within the same pipeline.
- Direct Fact: Mixing
only/exceptandrulesjobs in a single pipeline is strictly forbidden. - Impact Layer: While this may not trigger a YAML syntax error, it creates non-deterministic behavior. The different default evaluation methods of
only/except(which is legacy) andrules(which is modern) can lead to jobs running when they should be skipped, or vice versa. - Contextual Layer: To maintain pipeline integrity, developers must migrate all
only/exceptblocks to therulessyntax, utilizingwhen: neverto replicate theexceptfunctionality.
Analysis of Rule Evaluation Flow
The evaluation of when: never is not an isolated event but part of a sequential chain. The process follows this specific hierarchy:
- Workflow Rules: The
workflow: rulesblock is evaluated first. If this block evaluates towhen: never, the entire pipeline is discarded. - Job Rules: If the pipeline is created, the runner evaluates the
rulesof each individual job. - First Match Wins: The runner reads the rules from top to bottom. The first rule that evaluates to true determines the fate of the job.
- Default Behavior: If no rules match, the job is typically not added to the pipeline unless a default
whenis specified.
This sequential nature is why the placement of when: never is paramount. If a permissive rule (e.g., when: always) is placed above a restrictive rule (e.g., when: never), the restrictive rule will never be reached, and the job will execute.
Conclusion
The when: never directive is the primary mechanism for achieving precision in GitLab CI/CD pipeline execution. Its utility extends from simple job exclusion to complex global pipeline management via workflow: rules. The most significant challenges associated with when: never arise from the sequential nature of rule evaluation and the potential for duplicate pipelines when branch and merge request triggers overlap. By leveraging the CI_PIPELINE_SOURCE variable and ensuring a strict transition from legacy only/except syntax to the modern rules framework, DevOps engineers can eliminate redundant runs and optimize resource consumption. The introduction of CI/CD components in GitLab 16 further streamlines this process by allowing the encapsulation of these logical gates into reusable modules. Ultimately, the mastery of when: never is essential for any organization seeking to implement a robust DevSecOps workflow that is both efficient and predictable.