Using GitLab CI/CD only:changes to Run Jobs Only When Relevant Files Change
Learn how GitLab CI/CD's only:changes keyword skips jobs unless specified files are modified, with a working .gitlab-ci.yml example, limits, and verification steps.
17 Jun 2026, 17:27 UTC

Quick answer
Add the only: changes rule to a job so GitLab skips the job unless the pipeline touches files that match the paths you specify. This saves compute time when a commit only modifies unrelated files (e.g., documentation or assets).
How only:changes works
When a pipeline starts, GitLab compares the latest commit on the branch that triggered the pipeline with the tip of the project’s default branch (or the target branch for merge request pipelines). If any of the files changed in that diff match the glob patterns you list under only: changes, the job runs; otherwise the job is marked as skipped.
Example configuration
stages:
- test
lint:
stage: test
script:
- echo "Running Go linter…"
- golangci-lint run ./...
only:
changes:
- '**/*.go'
- 'go.mod'
- 'go.sum'
In this example, the lint job executes only when a commit modifies a Go source file (*.go) or one of the dependency manifest files (go.mod, go.sum). A commit that updates only README.md will cause the job to be skipped.
Limits and prerequisites
- The comparison relies on having a full commit history available to the runner. If the runner uses a shallow clone (e.g.,
GIT_DEPTH=1), GitLab cannot compute the diff and the rule always evaluates to false, causing the job to be skipped. - For pipelines on branches other than the default branch, GitLab compares against the target branch of the merge request. If the project’s default branch is not set, the comparison falls back to the repository’s HEAD, which may produce unexpected results.
- In forked projects,
only: changesis evaluated against the fork’s own default branch, not the upstream repository. Changes made only in the upstream will not trigger the job unless the fork is synchronized.
Common mistakes
- Missing default branch: If the project settings do not define a default branch, the comparison base becomes ambiguous. Fix: go to Settings → General → Default branch and set it (usually
mainormaster). - Overly broad glob patterns: Patterns like
'*'match every file, defeating the purpose. Use specific patterns (e.g.,'src/**/*.ts') to limit the scope. - Expecting detection on renamed files without history: If a file is renamed and the runner lacks the commit that shows the rename, GitLab may see it as a delete plus an add and still match the pattern, but with shallow clones the rename may be invisible. Ensure runners have enough depth (
GIT_DEPTH>=50) or disable shallow clones. - Assuming it works for all pipeline types: The rule does not apply to trigger pipelines or scheduled pipelines unless they are also branch pipelines; they always run because there is no branch diff to evaluate.
Verification steps
- Commit a change that matches one of the patterns (e.g., edit
main.go) and push to a branch. Open CI/CD → Pipelines and confirm thelintjob runs. - Commit a change that does not match any pattern (e.g., update
docs/README.md) and push. The same pipeline should show thelintjob as skipped. - Use the CI Lint tool (CI/CD → Lint) to validate the
.gitlab-ci.ymlsyntax. In the lint form, you can simulate changed files by entering them in the “Changed files” field to see whether the job would be considered.
Practical checklist
- Verify the project has a default branch set.
- Ensure runners are not using
GIT_DEPTH=1unless you are okay with jobs always being skipped. - Test with both matching and non‑matching commits in a merge request to see the behavior in the MR pipeline.
- Keep the glob patterns as specific as possible to avoid unintended triggers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.