Skip to content

title: phpboyscout/cicd v0.10.8 — only renovate-self runs on scheduled pipelines description: Every component except renovate-self gains a leading rules: guard that forces when: never on $CI_PIPELINE_SOURCE == "schedule". Scheduled pipelines exist to run Renovate; today the gate jobs (unconditional when: on_success) and the default-branch deploy/release jobs ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH) also fire on a schedule, adding pointless — and in release-plz's case, risky — churn to every consumer's Renovate run. status: approved date: 2026-06-21 authors: [Matt Cockayne] tags: [spec, cicd, components, rules, schedule, renovate]


Spec: phpboyscout/cicd v0.10.8 — schedule-pipeline scoping

  • Repository: gitlab.com/phpboyscout/cicd
  • Released as: v0.10.8 (patch — a fix across every component: correcting jobs that fire on scheduled pipelines when they should not).
  • Driver: every consumer (go-tool-base, rust-tool-base, keyrx, the terraform repos, …) drives Renovate from a GitLab pipeline schedule ($CI_PIPELINE_SOURCE == "schedule", $RENOVATE_TASK == "scan"). On that schedule only renovate-self should run — but other components currently fire too, because a schedule runs on a branch (normally main) and their rules match that context.

Problem

A scheduled pipeline runs on a branch — here the default branch (main) — with $CI_PIPELINE_SOURCE == "schedule", no $CI_COMMIT_TAG and no merge request. Evaluating the components' rules: under those conditions, two classes of job match when they should not:

  1. Gate jobs carry an unconditional rules: [{ when: on_success }] (the v0.4 change that made them visible in MR pipelines). An unconditional rule matches every pipeline — including a schedule. Affected: tofu-lint (tofu-fmt, tflint, terraform-docs-drift), tofu-security (trivy-config, checkov, gitleaks), tofu-validate, zensical-pages's zensical-build.

  2. Default-branch deploy / release jobs gate on $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH (or a deploy_branch literal) as a proxy for "a push landed on main". That expression also matches a schedule running on main. Affected: zensical-pages's pages deploy, tofu-plan, tofu-apply (local-plan mode), and release-plz (release-plz:pr + release-plz:release, whose default if: is the default branch).

Confirmed against the live consumers (schedule targets main):

Consumer Fires on the Renovate schedule (besides renovate-self)
go-tool-base zensical-build, pages deploy, releaser-pleaser (external)
keyrx zensical-build, pages deploy, releaser-pleaser (external)
rust-tool-base zensical-build, pages deploy, release-plz:pr + release-plz:release

The release-plz case is the most serious: running the release driver on a Renovate schedule can open/update a Release MR or publish off a schedule rather than off a real push to main.

Decisions

D1 — Every non-renovate-self job gets a leading schedule-never guard

Each job in every component except renovate-self gains, as the first rule:

rules:
  - if: '$CI_PIPELINE_SOURCE == "schedule"'
    when: never
  # …existing rules unchanged…

Rules evaluate top-down, first match wins. On a schedule the guard matches and the job is dropped; in every other context it falls through to the unchanged existing rules. This preserves the release-on-main behaviour the consumers depend on — release-plz / tofu-apply / pages still run on a real push to the default branch, they just stop running on the Renovate schedule.

renovate-self is the sole exception: its whole purpose is the schedule ($CI_PIPELINE_SOURCE == "schedule" && $RENOVATE_TASK == "scan"), so it keeps its rule untouched.

D2 — Uniform guard, applied even to already-safe jobs

MR-only jobs (merge_request_event) and tag-only jobs ($CI_COMMIT_TAG) can never match a schedule, so the guard is redundant for the default inputs. We add it anyway, uniformly, for three reasons:

  • One invariant, trivially auditable: "every component except renovate-self refuses a scheduled pipeline." A reviewer checks for the guard, not for a per-job argument about whether its source could ever be schedule.
  • Override-proof: the gate components and the go/rust tracks expose the trigger as an if: input. A consumer who widens that input cannot accidentally pull a job onto the Renovate schedule — the guard sits ahead of $[[ inputs.if ]].
  • Copy-paste correctness: the next component author copies the pattern without re-deriving the rule.

D3 — Placement relative to existing when: never enable-guards

Some jobs already lead with a disable rule (e.g. go-test-e2e's "$[[ inputs.enable_e2e ]]" != "true" → never, the rust opt-in test-macos / test-windows / test-integration / coverage jobs). The schedule guard goes before those, as the very first rule. Order among never guards is immaterial (any match drops the job), but "schedule guard first" keeps the pattern identical across all jobs.

D4 — Self-test: no change, no breakage

Component self-tests run inside child pipelines where $CI_PIPELINE_SOURCE is masked to parent_pipeline — never schedule — so the new guard is inert there and every existing self-test behaves exactly as before. Tests that already override a component's rules: wholesale (renovate-self, tofu-apply, tofu-module-publish) are unaffected because the override replaces the rules anyway. Same reasoning as v0.4 D3; the tests/ tree is unchanged.

D5 — External components are a consumer-side concern

apricote/releaser-pleaser (used by go-tool-base and keyrx) is not ours and still fires on the schedule. The fix there is a consumer-side rules: override on the releaser-pleaser job, tracked in the consumer pipelines, not in this component release. rust-tool-base uses our release-plz, so the component fix covers it directly.

D6 — Versioning

A fix(<component>): … across multiple components. Per Conventional Commits this is a patch; releaser-pleaser batches it into the next patch release (numbered v0.10.8 here, alongside the pending v0.10.7 renovate-self fix if they release together). Pre-1.0 caveat from v0.1 still applies.

Follow-up

  • Consumer-side: override the external releaser-pleaser job's rules in go-tool-base and keyrx to add the same schedule-never guard; bump every consumer's component pins to >= v0.10.8 (Renovate's preset manager does this automatically once the tag exists).
  • Task 2 (separate): audit the branch / tag / MR pipelines themselves — including the cicd repo's own root .gitlab-ci.yml self-test triggers, which fan out on a schedule via the $CI_PIPELINE_SOURCE == "schedule" workflow rule — to cut further churn beyond the scheduled case.