Defer config error when workflow:rules exist

What does this MR do and why?

Fixes an issue where a pipeline with 0 jobs is persisted as failed (red X on MR) when include:rules: filter out all includes, even though workflow:rules would have blocked the pipeline entirely.

Root cause

The pipeline chain evaluates steps in order:

  • Config::Process: Processes the YAML config. When include:rules: filter out all includes and no inline jobs exist, the YAML processor reports "jobs config should contain at least one visible job" as a config_error. This is a persistable failure reason, so the pipeline is saved to the database as failed.
  • EvaluateWorkflowRules: Evaluates workflow:rules. This step is never reached because the chain breaks at Config::Process.

If workflow:rules would have blocked the pipeline (e.g., when: never for the current source), the pipeline should not be persisted at all (filtered_by_workflow_rules is a non-persistable reason). But because Config::Process errors first, workflow:rules is never evaluated.

Fix

  1. YamlProcessor::Result: Always extract workflow attributes (workflow_rules, root_variables) and empty defaults for stages/jobs from the CI config, even when the result is invalid. Adds only_no_visible_jobs_error? so we defer only this specific error, never syntax or other config errors.

  2. Config::Entry::Jobs: Extract the error string into a NO_VISIBLE_JOBS_ERROR constant so the predicate matches on a shared constant, not a duplicated string.

  3. Config::Process: When the only error is "no visible jobs" and workflow:rules are present, set yaml_processor_result on the command and store the error as deferred_config_error instead of failing. This lets EvaluateWorkflowRules run.

  4. EvaluateWorkflowRules: If workflow:rules pass and a deferred_config_error is present, surface the original config_error (preserving the exact message and persistable behaviour). If workflow:rules filter the pipeline, it stays non-persistable (filtered_by_workflow_rules) and no pipeline is created.

Behaviour matrix

Scenario Before After
workflow:rules block + 0 visible jobs (include:rules filtered all) persisted config_error (red X) not persisted (filtered_by_workflow_rules)
workflow:rules allow + 0 visible jobs persisted config_error unchanged — persisted config_error with original message
Syntax / other config errors persisted config_error unchanged
Valid config normal unchanged

How to reproduce

  1. Project .gitlab-ci.yml:
    workflow:
      rules:
        - if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
          when: never
        - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == 'push'
    
    include:
      - component: $CI_SERVER_FQDN/group/component/template@main
        rules:
          - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
  2. Push to a non-default branch
  3. Before: Push pipeline created with 0 jobs, config_error, persisted as failed (red X)
  4. After: Pipeline not persisted (filtered_by_workflow_rules)

Reproduced on GitLab.com: https://gitlab.com/oyakovenko-group/broken-pipeline-591780/-/pipelines/2685242385

References

MR acceptance checklist

  • Unit tests for Config::Process, EvaluateWorkflowRules, and YamlProcessor::Result
  • Integration tests for the full pipeline chain
  • All existing specs pass
  • RuboCop clean
Edited by Oleg Yakovenko

Merge request reports

Loading
Loading