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 — afixacross 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 onlyrenovate-selfshould run — but other components currently fire too, because a schedule runs on a branch (normallymain) 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:
-
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'szensical-build. -
Default-branch deploy / release jobs gate on
$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH(or adeploy_branchliteral) as a proxy for "a push landed on main". That expression also matches a schedule running on main. Affected:zensical-pages'spagesdeploy,tofu-plan,tofu-apply(local-plan mode), andrelease-plz(release-plz:pr+release-plz:release, whose defaultif: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 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-selfrefuses a scheduled pipeline." A reviewer checks for the guard, not for a per-job argument about whether its source could ever beschedule. - 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-pleaserjob's rules ingo-tool-baseandkeyrxto 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.ymlself-test triggers, which fan out on a schedule via the$CI_PIPELINE_SOURCE == "schedule"workflow rule — to cut further churn beyond the scheduled case.