Skip to content
DevOps AI ToolKit
Newsletter
All prompts
AI for GitLab CI/CD Difficulty: Intermediate ClaudeChatGPT

GitLab CI/CD `rules:` Debugging Prompt

Diagnose why a GitLab job did or didn't run — decode `rules:` evaluation, `only/except` legacy syntax, workflow rules, and complex `$CI_*` variable conditions.

Target user
DevOps engineers debugging job triggering in GitLab pipelines
Difficulty
Intermediate
Tools
Claude, ChatGPT

The prompt

You are a senior DevOps engineer with deep experience reading GitLab `rules:` blocks. You know that GitLab evaluates rules top-to-bottom and the FIRST match wins — and that this is the single most common source of "why didn't my job run?" confusion.

I will provide:
- The job(s) in question and whether they "should have run" or "shouldn't have run"
- The full `.gitlab-ci.yml` `workflow:` block (if present)
- The job's `rules:` (or `only:`/`except:`)
- The pipeline source (push, merge_request_event, schedule, web, api, trigger, parent_pipeline)
- The branch name, commit SHA, MR ID (if any)
- The values of any `$CI_*` variables the rules reference
- The actual pipeline outcome: pipeline created? job present? job skipped? job manual? job allow_failure?

Your job:

1. **Resolve the pipeline source** first. `rules:` interact with `$CI_PIPELINE_SOURCE`, which can be:
   - `push` (default branch push, regular branch push)
   - `merge_request_event` (only when MR pipelines are enabled and an MR exists)
   - `schedule` (pipeline schedules)
   - `web` (manually run from UI)
   - `api`, `trigger`, `pipeline` (parent), `chat`, `external_pull_request_event`
   - **Push pipelines run by default; MR pipelines only run when configured.** Many "why two pipelines?" questions are detached MR + branch pipelines.
2. **Evaluate workflow rules** (if any):
   - `workflow:rules:` decide whether the WHOLE pipeline runs
   - If no `workflow:rules:` rule matches AND there's a `workflow:`, the pipeline is rejected entirely
3. **For each job's `rules:` block**, evaluate top-to-bottom:
   - First match wins. Subsequent rules are ignored.
   - A rule with `if:` matching but no `when:` defaults to `when: on_success`
   - `when: never` rule that matches skips the job
   - `when: manual` rule that matches adds the job but as manual
   - If no rule matches, the job is **excluded from the pipeline entirely**
4. **Common rule traps to surface**:
   - **`rules: -if: '$CI_PIPELINE_SOURCE == "merge_request_event"' -if: '$CI_COMMIT_BRANCH'`** runs BOTH on MR AND on branch pushes — duplicate pipelines, common confusion
   - **`only:` and `rules:` in the same job** → `rules:` wins; `only:` ignored
   - **`changes:`** without a parent rule context → applies to the rule only it's nested under
   - **`exists:`** evaluates against the FIRST matching commit's tree, may differ from working dir
   - **Variable expansion in `if:`** uses `$VAR` notation; comparisons need quoting (`'$VAR == "foo"'`)
   - **`$CI_COMMIT_BRANCH` is empty on tag pipelines AND on MR pipelines** — use `$CI_COMMIT_REF_NAME` if you need both
   - **MR pipelines need `merge_request_event` source** — branch-name rules like `if: $CI_COMMIT_BRANCH == "main"` never match on MR pipelines
5. **For `workflow:rules:` debugging**:
   - Default behavior (no workflow) creates pipelines on every push and on MR events if `Pipelines for merge requests` is enabled in project settings
   - A `workflow:rules:` block REPLACES the default; you need to explicitly allow what you want
   - Common pattern: prevent duplicate detached/branch pipelines:
     ```yaml
     workflow:
       rules:
         - if: $CI_PIPELINE_SOURCE == "merge_request_event"
         - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
           when: never
         - if: $CI_COMMIT_BRANCH
     ```
6. **Distinguish "job didn't run" from "job ran but no work happened"**:
   - Not in pipeline at all: rules excluded it
   - In pipeline as "skipped": rule matched with `when: never`
   - In pipeline as "manual": rule matched with `when: manual`
   - Ran, succeeded with no output: script issue, not rules
7. **Provide the FIRST rule that matches** in your output — explain why it matched and why subsequent rules don't apply.

Be exact about which `$CI_*` variable matters; many users mistype them.

---

Question: [should-have-run / should-NOT-have-run]
Pipeline context:
  - Source: [push / merge_request_event / schedule / web / api / trigger]
  - Branch / Tag / MR: [DESCRIBE]
  - Project: [DESCRIBE]
Job(s) involved: [name]
`workflow:rules:` block (if present):
```yaml
[PASTE]
```
Job's `rules:` (or `only:`/`except:`):
```yaml
[PASTE]
```
Relevant `$CI_*` variable values at evaluation time:
```
[PASTE]
```
Pipeline outcome (UI screenshot text):
[DESCRIBE]

Run this prompt with AI

Test it, get an AI-improved version, or compare models — live in the Prompt Workspace. No copy-paste.

Why this prompt works

rules: evaluation is deterministic but unintuitive — first match wins, falling-off-the-end excludes the job, and the variables available change based on pipeline source. Most “my job didn’t run” debugging conversations boil down to: which rule matched first, and why. This prompt forces an explicit walkthrough.

How to use it

  1. Always include the pipeline source. A rule that works on push may not work on MR.
  2. Capture the $CI_* variable values from the actual pipeline run. GitLab UI: pipeline → job → “Variables” tab.
  3. Include workflow:rules: even if you don’t think it’s relevant. If workflow rules reject the pipeline, no job rules matter.
  4. For MR pipelines specifically: confirm “Pipelines for merge requests” is enabled in the project’s CI/CD settings.

Useful commands

# Get the variables for a pipeline run (admin or maintainer)
curl -s --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/projects/<id>/pipelines/<pid>/variables" | jq

# Get pipeline details (source, ref, etc.)
curl -s --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/projects/<id>/pipelines/<pid>" | jq

# Get all jobs in a pipeline (see what was included/excluded)
curl -s --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.example.com/api/v4/projects/<id>/pipelines/<pid>/jobs" | \
  jq -r '.[] | "\(.status)\t\(.name)\t\(.stage)"' | column -t

# Lint the yaml (locally)
# In project: CI/CD → Pipeline editor → "Validate" tab
curl -s --header "PRIVATE-TOKEN: <token>" --header "Content-Type: application/json" \
  --data "{\"content\": $(cat .gitlab-ci.yml | jq -Rs)}" \
  "https://gitlab.example.com/api/v4/ci/lint" | jq

# Get the merged YAML (after `include:` resolution) for debugging
# Project → CI/CD → Pipeline editor → "Full configuration" tab

Predefined variable cheatsheet (most-misused)

VariableSet onEmpty on
$CI_COMMIT_BRANCHBranch pipelinesMR pipelines, tag pipelines
$CI_MERGE_REQUEST_IIDMR pipelinesBranch/tag pipelines
$CI_COMMIT_TAGTag pipelinesBranch pipelines, MR pipelines
$CI_COMMIT_REF_NAMEAll — branch or tag nameDetached pipelines (rare)
$CI_PIPELINE_SOURCEAllNever (always set)
$CI_DEFAULT_BRANCHAll — name of default branchNever
$CI_OPEN_MERGE_REQUESTSWhen branch has open MR(s)When branch has no open MR

Common rule patterns

Run on default branch only

deploy-prod:
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Run on MR only

test:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Run on MR OR on default branch — but never both

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
      when: never            # skip branch pipeline if MR exists
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Run only on tag pipelines, with manual approval

release:
  rules:
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
      when: manual

Run only when frontend changes

build-frontend:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - frontend/**/*
        - package.json

Skip on docs-only commits

workflow:
  rules:
    - if: $CI_COMMIT_MESSAGE =~ /\[skip ci\]/i
      when: never
    - when: always

Common findings this catches

  • Job has only: main AND rules:rules: wins; only: is silently ignored. Remove only:.
  • rules: - if: $CI_COMMIT_BRANCH == "main" but pipeline is from MR event → $CI_COMMIT_BRANCH is empty on MR pipelines. Use $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main".
  • workflow:rules: excludes branch pipelines, then someone wonders why pushing to a branch doesn’t run anything → workflow rules need explicit allow for branches.
  • Two pipelines per push when MR is open → standard duplicate-pipeline situation. Apply the workflow rule above.
  • rules:changes: on first commit of a branch → first commit “changes” everything; rule always matches. Use compare_to: to compare against default branch.
  • when: manual rule matches but pipeline shows the job as auto-running → there’s an earlier rule (above in the list) that matched first with default when: on_success. First match wins.

Migration: only/exceptrules:

# OLD
only:
  - main
  - tags
except:
  - schedules
# NEW
rules:
  - if: $CI_PIPELINE_SOURCE == "schedule"
    when: never
  - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  - if: $CI_COMMIT_TAG

When to escalate

  • Behavior that contradicts the rules-as-written → likely a GitLab version-specific change; check release notes (or your GitLab version’s CHANGELOG).
  • “Pipelines for merge requests” toggle disabled at project level — administrative; rules referencing merge_request_event won’t ever match.
  • Custom workflow with includes from a remote template — confirm the merged YAML, not just your local file.

Related prompts

More GitLab CI/CD prompts & error guides

Browse every GitLab CI/CD prompt and troubleshooting guide in one place.

Free download · 368-page PDF

Reading prompts? Get all 500 in one free PDF

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.