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.
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.
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
- Preserve evidence: configuration SHA, pipeline IDs, source/ref/SHA, MR state, and lint output.
- Identify scope: did no pipeline exist, did the pipeline exist with a missing job, or did a job exist but fail/pending?
- Inspect resolved configuration: includes and workflow can change behavior outside the local snippet.
- Evaluate variables and rule order: use only documented creation-time variables for that source.
-
Inspect comparison context: target branch,
previous commit, explicit
compare_to, or no push event. -
Choose the least destructive correction: do not
add broad
when: always,allow_failure, or runner privileges to hide a creation defect. - 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.
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.
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?
Runner selection occurs after pipeline/job creation; inspect workflow/config/source first.
What evidence distinguishes duplicate pipelines from duplicate jobs?
Two different pipeline IDs/sources for the same SHA versus one pipeline containing multiple/unexpected jobs.
Why is CI_COMMIT_BRANCH not reliable for MR-only rules?
It is unavailable in merge-request pipelines.
A later deny rule never applies. What GitLab rule semantic explains this?
Rules are evaluated in order and stop at the first match.
Why preserve generated-child.yml when a trigger fails?
It is the executable configuration artifact that caused the parse/trigger result; preserving it makes the failure reproducible.
Why is “add when: always” often a bad diagnostic fix?
It broadens inclusion and can create duplicates or opt into unintended sources without addressing the actual predicate error.
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
- GitLab Docs — workflow keyword
- GitLab Docs — Specify when jobs run with rules
- GitLab Docs — CI/CD YAML syntax reference
- GitLab Docs — Predefined CI/CD variables
- GitLab Docs — Where variables can be used
- GitLab Docs — Merge request pipelines
- GitLab Docs — Downstream pipelines
- GitLab Docs — Pipeline editor
- GitLab Docs — CI Lint
- GitLab Docs — CI/CD pipelines
- GitLab Docs — Pipelines API
- GitLab Docs — Use CI/CD configuration from other files
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.