Skip to content

Which pipelines each component runs on

Every component carries its own rules:. Nothing here is inherited from your pipeline, and including a component does not mean its job appears on every pipeline — most of them are deliberately silent on most pipeline types. This page is the lookup table for which.

Two things decide whether a job appears:

  1. The component's own rules:, fixed in the template.
  2. Your workflow: rules, which decide whether the pipeline exists at all. A component cannot run on a pipeline your workflow: never created.

The trigger matrix

Columns are GitLab pipeline sources. MR is merge_request_event; branch is a push to a non-default branch with no merge-request pipeline; default is a push to $CI_DEFAULT_BRANCH; tag is a tag push; schedule is a scheduled pipeline.

Component MR branch default tag schedule
go-lint ✅ — — — ❌
go-test ✅ — — — ❌
go-security ✅ — — — ❌
go-singleuse ✅ — — — ❌
go-core-currency ✅ — — — ❌
goreleaser — — — ✅ ❌
image-release — — — ✅ ❌
image-retag — — — ✅ ❌
image-scan ✅ ✅ ✅ ✅ ✅
rust-lint ✅ — — — ❌
rust-test ✅ — — — ❌
rust-docs ✅ — — — ❌
rust-security ✅ — — — ❌
release-plz — — ✅ — ❌
svelte-build ✅ — — ✅ ❌
svelte-lint ✅ — — — ❌
svelte-test ✅ — — — ❌
svelte-security ✅ — — — ❌
docs-verify ✅ — — — ❌
skill-security ✅ — — — ❌
tofu-lint ✅ ✅ ✅ ❌ ❌
tofu-security ✅ ✅ ✅ ❌ ❌
tofu-validate ✅ ✅ ✅ ❌ ❌
tofu-plan ✅ — ✅ — ❌
tofu-apply (plan_source: job) — — ✅ — ❌
tofu-apply (plan_source: ref) — — — ✅ ❌
tofu-stop — — ✅ (manual) — ❌
tofu-deploy-generate (mode: generate) — — — ✅ ❌
tofu-deploy-generate (mode: verify-fixtures) ✅ — ✅ — ❌
tofu-module-publish — — — ✅ ❌
zensical-pages (zensical-build) ✅ ✅ ✅ ❌ ❌
zensical-pages (pages) — — ✅ — ❌
hugo-pages (hugo-build) ✅ ✅ ✅ ❌ ✅
hugo-pages (pages) ❌ — ✅ — ✅
colophon (colophon-publish, colophon-propose-next) — — ✅ — ❌
colophon (colophon-apply) — — ✅ — ❌
colophon (colophon-release-check) ✅ — — — ❌
colophon (colophon-release, assets: true) — — — ✅ ❌
releaser-pleaser — — ✅ — ❌
release-stamp — — — ✅ ❌
renovate-self — — — — ✅
renovate-group — — — — ✅
renovate-merge — — — — ✅
release-train — — — — ✅
verify-release-binaries — — — — ✅

✅ runs · — no rule matches, so no job is created · ❌ an explicit when: never suppresses it

Notes on individual rows:

  • image-scan's two scan jobs run everywhere by default (if is $CI_COMMIT_SHA, always true). scan:report is narrower: it exists only when report_project is set and runs only where report_if matches — default-branch pushes and schedules — so a merge-request pipeline never writes a branch build's findings to the dashboard.
  • hugo-build's MR and branch runs come from mr_gate, which defaults to true. That rule carries no pipeline-source condition, so the build gate fires on a merge request and on a plain branch push whenever a changes path was touched. Setting mr_gate: false (the loose direct-to-main mode) turns both columns into —.
  • tofu-apply and tofu-deploy-generate are one job, not two. plan_source and mode select a hidden job that carries the rules:, so choosing the mode chooses the trigger. Two rows, one job.
  • renovate-group also runs off-schedule when $RENOVATE_TARGET_FILTER is set — the manual-run escape hatch for a single repo, on any pipeline its if input matches.
  • colophon is five jobs, and only three of them share a trigger. colophon-publish and colophon-propose-next both run on the default branch, and the split between them is load-bearing rather than cosmetic: propose reads the last release from the tags in its own checkout, so it sees the tag publish just pushed only because it is a separate job whose fetch happens afterwards. colophon-release-check is narrower than its ✅ suggests — it runs on a merge request only when the source branch matches colophon/release/*, so it appears on the Release MR and nowhere else. colophon-release is off unless assets: true, and then fires on a tag matching tag_pattern (^v[0-9]+\.[0-9]+\.[0-9]+$ by default), which is what keeps a nested lint/v0.7.0-style tag from starting a release on a repo that publishes binaries.
  • colophon-apply shares the default-branch trigger and nothing else. It is the only job in the component that runs when a merge lands rather than when a release is cut, and it has no ordering relationship with the publish and propose pair — it neither reads nor writes a tag. It also ships allow_failure: true, so a failure to start shows as a yellow job rather than a red default branch: by the time it runs the merge has already happened and there is nothing in the pipeline to go back and fix.
  • The default-branch column assumes your workflow: creates a default-branch pipeline. Many repositories only run one so that a release driver can maintain its Release MR.

Why almost no component runs on a scheduled pipeline

Every other component's first rule is if: $CI_PIPELINE_SOURCE == "schedule" → when: never, unconditionally, before anything a consumer can influence.

A scheduled pipeline exists to do one job — run Renovate, or rebuild a time-sensitive site. Without the guard, every gate in the repository would also fire on that schedule: a full lint/test/security fan-out, nightly, on a commit nobody pushed and nobody is waiting on. On a fixed-concurrency self-hosted runner that is capacity taken from pipelines somebody is waiting on.

The guard is first and unconditional so that widening a component's if input cannot pull the job back onto the schedule. renovate-self, renovate-group, renovate-merge, release-train and verify-release-binaries have no guard because the schedule is the only place they are meant to run; hugo-pages deliberately runs on a schedule so future-dated content publishes without a push.

release-train is the exception among the four: a scheduled run only ever plans, and it refuses to execute on a schedule even when explicitly told to, because cutting releases is a per-train decision and never a standing one.

Why change-detection never applies to a tag or a schedule

rules:changes compares a diff, and a tag or scheduled pipeline has no meaningful diff to compare — GitLab treats the filter as always-true there. Filtering a release job on something that is always true is not a filter, and filtering it on something unreliable would occasionally skip a release. So changes: is attached only to merge-request rules (and, for the static-site components, to the branch rule that feeds a deploy).

The reasoning is in Explanation: change-detection; the recipe is Add change-detection to a gate.

Which components let you change when they run

Only some. A component with an if input exposes its trigger condition; a component without one has its trigger welded to the job it does, because the trigger and the work are one decision.

Has an if input (overridable) No if input (fixed trigger)
go-lint, go-test, go-security, go-singleuse all eight tofu-* components
rust-lint, rust-test, rust-docs, rust-security goreleaser
svelte-build, svelte-lint, svelte-test, svelte-security zensical-pages, hugo-pages
skill-security
release-plz, releaser-pleaser, colophon (three of five jobs — see below)
renovate-self, renovate-group, renovate-merge
release-train, release-stamp

The fixed ones are fixed for a reason worth knowing:

  • tofu-plan / tofu-apply authenticate by OIDC, and the AWS trust policy only accepts subjects from the refs it was written for. A job moved to a ref the trust policy does not name does not run a wrong plan — it fails at assume-role.
  • tofu-apply's trigger is coupled to plan_source: needs: and rules: both live in a per-mode hidden job the component extends, so choosing job or ref is choosing the trigger.
  • goreleaser and tofu-module-publish publish artefacts. Their tag match is the release gate; making it overridable would make it possible to publish from a branch.
  • colophon's if input reaches three of its five jobs. It gates colophon-publish, colophon-propose-next and colophon-apply — set it when you release from a non-default branch. It does not reach colophon-release-check or colophon-release, whose triggers are welded for the same reason goreleaser's is: one inspects the Release MR and the other publishes a release, and neither is meaningful anywhere else.

Widening a trigger you can widen still cannot reach a scheduled pipeline — see above.

Why the Tofu gates run on branch pushes and the Go, Rust and Svelte gates do not

This is a real difference, not a documentation slip. tofu-lint, tofu-security, tofu-validate and the zensical-pages build job have no if: on their run rule at all — they exclude schedules and tags and then run on whatever is left, which includes a plain branch push. The Go, Rust and Svelte gates default their if input to $CI_PIPELINE_SOURCE == "merge_request_event" and so run on merge requests only.

On a project with the usual workflow: dedup rule this is invisible, because no branch pipeline is created once a merge request is open:

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
      when: never
    - if: '$CI_COMMIT_TAG'
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    - if: '$CI_PIPELINE_SOURCE == "schedule"'

Without that dedup rule, a push to a branch that already has an open merge request runs the Tofu gates twice — once on the branch pipeline, once on the merge-request pipeline. Keep the $CI_COMMIT_BRANCH guard on the never rule: keying it on $CI_PIPELINE_SOURCE == "push" also matches a tag push and suppresses the entire tag pipeline, which is how a release silently stops publishing.

What "no rule matched" looks like

A component whose rules do not match produces no job. There is no skipped job, no grey icon, no entry in the pipeline graph — the job simply is not there. The pipeline goes green having run nothing.

That is the failure mode to expect when a component appears to have been ignored, and it is the first thing to check: not "why did my job fail" but "is my job in the pipeline at all". See Diagnose a job that did not run.

See also