GitLab CI When Never Logic and Pipeline Control

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: never directive 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 rules syntax, which replaces the older only/except logic. Because rules are evaluated in order, placing a when: never rule 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_SOURCE is used to control job inclusion based on the trigger event.
  • Impact Layer: Users can ensure that deployment jobs only run during merge_request_event and never during a standard push, 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 except trigger or schedule.

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: always in job rules without corresponding workflow: rules can 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: rules block 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: never if 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_live job only runs if changes occur in folder1/**/* or folder2/**/*.
  • Contextual Layer: There is a known risk where a job might execute even when a when: never rule for a specific source (like trigger) 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: rules specifying when: never for $CI_COMMIT_TAG are 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 workflow block versus job-level rules. In some cases, the ci.skip push 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: never for 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_SOURCE variable.

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/except and rules jobs 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) and rules (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/except blocks to the rules syntax, utilizing when: never to replicate the except functionality.

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:

  1. Workflow Rules: The workflow: rules block is evaluated first. If this block evaluates to when: never, the entire pipeline is discarded.
  2. Job Rules: If the pipeline is created, the runner evaluates the rules of each individual job.
  3. 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.
  4. Default Behavior: If no rules match, the job is typically not added to the pipeline unless a default when is 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.

Sources

  1. Microfluidics University of Toronto
  2. GitLab Forum - Workflow Rules Issue
  3. GitLab Forum - Trigger Rules
  4. GitLab Documentation - Job Rules
  5. Mozilla Developer Network - Optimizing DevSecOps

Related Posts