Use GitLab CI rules to Control When Jobs Run
Replace only/except with rules to explicitly control which GitLab CI jobs run in merge request and push pipelines.
13 Jan 2026, 13:24 UTC

A job you didn’t ask for
You add a lint job to .gitlab-ci.yml. It runs on every push to your feature branch, but you only want it in merge request pipelines. Or worse, a deploy job appears on the default branch because an old only clause still matches. GitLab CI creates jobs based on conditions that are not always obvious, and debugging why a job appeared—or didn’t—often means reading pipeline logs after the fact.
The useful takeaway: use the rules keyword to declare job conditions explicitly, stop mixing it with only and except, and verify behavior before you merge.
How rules decides whether a job exists
rules controls whether GitLab creates a CI job and when it runs. GitLab evaluates each rule in order. The first rule that matches sets the job’s action, including when and allow_failure. If no rule matches, the job is omitted from the pipeline entirely. A rule with when: never explicitly excludes the job, which is useful for overriding a broader rule above it.
GitLab recommends rules over the older only and except syntax. The older syntax still works in many releases, but it is not receiving new features. Do not combine only/except with rules in the same job; GitLab treats that combination as unsupported and may create or skip jobs unexpectedly.
Exact deprecation timing for only/except varies by GitLab self-managed version and SaaS release. Check your instance version before standardizing on rules across teams.
Worked example: run lint only in merge requests
This configuration runs a lint job in merge request pipelines, but skips it on ordinary pushes to the default branch.
lint:
script:
- npm run lint
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
when: always
- when: never
Place this in your project’s .gitlab-ci.yml at the repository root. Editing CI configuration typically requires at least the Developer role on the project; Maintainer is required to protect branches or change some CI settings in self-managed instances.
Here is what happens:
- A merge request triggers the first rule. The job is created and runs.
- A push to
maindoes not match the first rule. The second rule matches because it has noifcondition, so the job is omitted withwhen: never.
Do not add an only: block to the same job. If you need to migrate an existing job, replace its only/except block entirely with rules.
The rules:changes comparison base changes by pipeline type
rules:changes runs a job only when listed files changed. The comparison base depends on how the pipeline started:
- Merge request pipelines compare the source branch against the target branch.
- Push pipelines compare the latest commit against the previous commit.
That means the same file change can cause a job to run in a merge request pipeline but stay skipped in a push pipeline, or vice versa. If you use rules:changes for a review job, test it from both a merge request and a direct push to a feature branch before relying on it.
Verify before you merge
Use GitLab’s CI Lint tool to validate syntax and preview job expansion without creating a commit or pipeline. In your project, go to CI/CD > Editor > CI Lint, paste your .gitlab-ci.yml content, and inspect the expanded jobs. This does not change any pipeline state.
For runtime confirmation, add a temporary debug job to a feature branch:
debug-rules:
script:
- echo "Pipeline source: $CI_PIPELINE_SOURCE"
- echo "Branch: $CI_COMMIT_BRANCH"
rules:
- when: always
Run a pipeline on the feature branch, then create a merge request from that branch. Check the pipeline graph to confirm whether the debug job appears in each case. Remove the debug job after verification. Do not leave temporary jobs that echo environment variables in production branches; they can expose CI variables in job logs.
Check your GitLab instance version. On self-managed instances, select Help > Version. On GitLab.com, review the current release notes. Compare that version against the behavior you need before adopting rules as a team standard.
Limitation
rules does not explain why a job was skipped in the pipeline UI; it only shows that the job did not run. You may need to inspect the YAML and pipeline source to understand which rule matched. Treat version-specific behavior as unverified until you confirm it on your instance.
Closing
Replace implicit scheduling with explicit rules. Validate with CI Lint, confirm behavior across merge request and push pipelines, and remove temporary debug jobs before they reach default branches.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.