Chapter 12Lesson 04~235 minutes

rules, workflow, Pipeline Sources, Changes, Conditions, and Dynamic Pipeline Creation: Diagnostics, Failure Modes, Security, and Performance

Diagnose duplicate or missing pipelines, unavailable source variables, misleading changes comparisons, unreachable rules, and invalid or untrusted generated child configuration without masking the original cause.

DiagnosticsMissing pipelineDuplicate pipelineVariable scopeDynamic configTrust

Learning objectives

  • Use a creation-time diagnostic sequence before looking at runner logs.
  • Distinguish duplicate pipeline creation from duplicate job inclusion.
  • Diagnose rules that depend on variables unavailable in the current source.
  • Interpret rules:changes comparison semantics instead of treating them as filesystem glob checks alone.
  • Preserve and validate generated child YAML before trusting it as executable pipeline policy.
Availability baseline (verified 2026-08-21). workflow:rules, job rules, rules:if, rules:changes, rules:exists, parent-child pipelines, and dynamic child pipelines are core GitLab CI/CD capabilities on Free, Premium, and Ultimate across GitLab.com, Self-Managed, and Dedicated. Live execution still requires eligible runner capacity; every mandatory exercise therefore has a CI Lint/merged-configuration and prediction path that does not require buying compute. Version-sensitive additions are labeled: directory matching for rules:exists arrived in GitLab 18.2, and rules:changes:regexp is new in GitLab 19.2 and is not required for this chapter.

1. Diagnostic sequence: preserve → scope → evaluate → repair → verify

  1. Preserve evidence: configuration SHA, pipeline IDs, source/ref/SHA, MR state, and lint output.
  2. Identify scope: did no pipeline exist, did the pipeline exist with a missing job, or did a job exist but fail/pending?
  3. Inspect resolved configuration: includes and workflow can change behavior outside the local snippet.
  4. Evaluate variables and rule order: use only documented creation-time variables for that source.
  5. Inspect comparison context: target branch, previous commit, explicit compare_to, or no push event.
  6. Choose the least destructive correction: do not add broad when: always, allow_failure, or runner privileges to hide a creation defect.
  7. Verify independently: reproduce with the same source category and compare source/SHA/job set.

2. First classify the missing object

Observed state Likely layer Do not start with
No pipeline object after event workflow:rules, include/config validity, event/source eligibility. Runner tags or job script.
Pipeline exists, job absent Job rules, first-match logic, variable/change/exists predicate. Runner registration.
Job exists, status pending Runner eligibility/tags/capacity/protection. workflow rules.
Job starts and fails Script/runtime/service/artifact layer. Pipeline-source rule unless wrong job should not have existed.
Two pipelines same SHA Whole-pipeline overlap, often branch + MR creation. Duplicating job-level negative rules everywhere.

3. Broken example: branch and MR pipelines both run

A common anti-pattern is relying on job rules with a broad fallback while allowing both pipeline types to exist:

test:
  script: echo test
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - when: always

A push to a branch with an open MR can yield a branch pipeline and an MR pipeline. Preserve both pipeline IDs and sources. Repair the whole-pipeline policy with workflow:rules that switches from branch to MR pipelines, rather than adding increasingly complex exclusions to every job.

4. Broken example: using a variable unavailable in this source

mr_only:
  script: echo "MR"
  rules:
    - if: '$CI_COMMIT_BRANCH == "feature/demo"' 

If the intent is an MR job, this predicate is conceptually wrong because CI_COMMIT_BRANCH is not available in MR pipelines. Repair the source identity first:

rules:
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "feature/demo"' 

5. Broken example: changes assumed to mean “since last human edit”

A scheduled or manually started pipeline is configured with only:

nightly_backend:
  script: ./test-backend.sh
  rules:
    - changes:
        - backend/**/*

The author expects the job to skip because no one pushed backend code immediately before the schedule. That is not a defined comparison model for this source. For pipelines without an associated push, changes without compare_to can evaluate true. Repair by making the schedule intent explicit (often run it unconditionally) or specify a deliberate comparison base.

6. Broken example: the later rule can never win

deploy_review:
  script: echo deploy
  rules:
    - if: '$CI_COMMIT_BRANCH'
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: never

On the default branch, the first rule already matches, so the deny rule is unreachable. Move the specific deny above the broad branch allow, or express the intended condition in one rule.

7. Broken example: generated child configuration is invalid

The generator job succeeds and uploads generated-child.yml, but the trigger job fails because the artifact is not valid GitLab CI configuration. Preserve the artifact rather than regenerating it immediately. Validate its YAML and GitLab CI schema exactly as you would a committed configuration.

# Intentionally invalid generated artifact fixture:
child_probe:
  scripts:                # wrong key; GitLab expects script
    - echo "hello"

Repair the generator template to emit script, regenerate under a new commit/pipeline, and retain the failed artifact/pipeline ID as evidence of the original cause.

8. Security failure: generated YAML trusts unreviewed input

A more serious defect is syntactically valid generated YAML derived from untrusted input that can select images, runner tags, includes, or commands. Do not “sanitize by regex and hope.” Define an allowlisted data model (for example component names from a committed manifest), generate only known YAML structures, and ensure untrusted pipelines cannot access protected variables/runners/environments.

Never solve a rule/configuration problem by exposing more credentials, enabling privileged runners, bypassing protected refs, or moving secrets into unprotected variables.

9. Evidence bundle for a rule incident

  • Commit SHA containing the CI configuration.
  • Pipeline ID(s), source, ref, SHA, status, and creation timestamp.
  • MR IID/source/target branch when relevant.
  • CI Lint or merged-configuration result.
  • The exact ordered rules involved, with the first matching rule identified.
  • For changes, the intended and actual comparison base.
  • For dynamic children, generator job ID, generated YAML artifact checksum/content, trigger job state, and child pipeline ID if created.

10. Performance and cost are causal, not generic warnings

Duplicate pipelines multiply jobs immediately. Broad changes/exists patterns also consume configuration-evaluation work; GitLab documents limits and fail-open matching behavior for very large match workloads. Dynamic child pipelines can reduce a huge static graph, but generation and additional pipeline orchestration have cost. Measure pipeline count and job count per source before optimizing syntax.

11. Symptom → smallest repair

Symptom Smallest causal repair
Branch + MR duplicate pipelines Add/repair workflow source switching; do not patch every job.
MR job absent because branch variable test fails Use MR source + MR-specific variable.
Scheduled changes job runs unexpectedly Use explicit schedule rule or compare_to with deliberate semantics.
Specific rule never applies Reorder rules so specific match precedes broad match.
Generated child invalid Fix generator output and validate artifact before trigger.
Generated child uses untrusted control data Constrain/allowlist generator inputs and isolate privileged resources.

Knowledge check

No pipeline exists. Why is checking runner tags premature?

What evidence distinguishes duplicate pipelines from duplicate jobs?

Why is CI_COMMIT_BRANCH not reliable for MR-only rules?

A later deny rule never applies. What GitLab rule semantic explains this?

Why preserve generated-child.yml when a trigger fails?

Why is “add when: always” often a bad diagnostic fix?

Summary

Creation-time debugging starts by identifying which object is missing or duplicated, then reading source/ref/SHA, resolved configuration, variable availability, rule order, and comparison base. Dynamic pipeline failures add one more artifact—the generated configuration—which must be preserved and reviewed as code.

Official references

Next lesson

Checkpoint the entire pipeline-source matrix

Lesson 5 predicts push, MR, tag, and web outcomes before execution, injects one duplicate/missing-pipeline defect, repairs it without a catch-all rule, and records a minimal production rule contract.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.