ci(renovate): run hourly so auto-merge can actually fire; squash strategy #51

Merged
ullrich merged 1 commit from ci/renovate-automerge-strategy-and-hourly into main 2026-07-29 14:14:45 +00:00
Owner

Answers "do I need to do anything?" — no, but the config did.

The diagnosis, and my wrong guess

I suspected automergeStrategy was unsupported and that Renovate had therefore "stopped automerging". A debug dry-run disproved that:

DEBUG: Branch status green            (branch=renovate/picomatch-…)
INFO:  DRY-RUN: Would merge PR #33 with strategy "auto"

Nothing was blocked. Renovate was willing to merge the instant it looked — it just had no run in which to look while CI was green.

platformAutomerge only enqueues at PR creation, so for an already-open PR Renovate does the merging itself, from inside a run. On the weekly cron that meant waiting up to a week.

The rebaseWhen trap I shipped in #34

Worse than a plain wait, and my fault. rebaseWhen: "behind-base-branch" re-pushes any branch whose base has moved, which restarts CI — so the branch goes green ~10 minutes after the run that touched it has already passed it by. Observed precisely that:

time event
06:38 main moved (#31, #34 merged)
07:17–07:23 Renovate rebased four branches → CI restarted
07:34 run ended, having already evaluated them
~07:27+ CI went green — too late

Repeat weekly and, whenever main moves between runs, the safeguard I added to close the stale-base gap defers the merge it was guarding, potentially forever.

Changes

Hourly cron (0 * * * *) — bounds the wait to an hour and largely defuses the starvation. Most runs find nothing to open, because renovate.json5's own schedule still restricts PR creation to Monday mornings (docker images monthly). Waking up hourly is about merging, not creating.

automergeSchedule: ["at any time"] — explicit, so merging never inherits the weekly creation window rather than relying on that staying the default.

automergeStrategy: "squash" — a preference, not part of the fix, and the first draft of this PR wrongly justified it as required. One commit per dependency update on main instead of a merge commit plus parent: easier to read, easier to revert, and it matches the squash policy CONTRIBUTING.md documents for non-human contributions.

Verified rather than assumed: the repo allows squash (allow_squash_merge: true), and Forgejo signs squash commits with the instance key (repository.signing.MERGES: always), so require_signed_commits on main stays satisfied without Renovate holding a signing key.

Validated

Config parses via json5 with all five keys resolving on the auto-merge rule (squash, at any time, platformAutomerge: true, 3 days, behind-base-branch, types minor/patch/pin/digest). Workflow on: block still has exactly one cron and workflow_dispatch intact.

Worth watching

A full run that opens PRs took ~21 minutes, and there are two runner slots shared with CI. No-op runs are far cheaper, but if CI starts queuing behind Renovate, dial this to every few hours rather than back to weekly.

Once this merges, #33 and #35 should merge on their own within the hour — both are green and up to date. Note they touch pnpm-lock.yaml, which is in cd-apps' path filter, so each will trigger a production deploy.

Answers "do I need to do anything?" — no, but the config did. ## The diagnosis, and my wrong guess I suspected `automergeStrategy` was unsupported and that Renovate had therefore "stopped automerging". **A debug dry-run disproved that:** ``` DEBUG: Branch status green (branch=renovate/picomatch-…) INFO: DRY-RUN: Would merge PR #33 with strategy "auto" ``` Nothing was blocked. Renovate was willing to merge the instant it looked — it just had **no run in which to look while CI was green**. `platformAutomerge` only enqueues at PR *creation*, so for an already-open PR Renovate does the merging itself, from inside a run. On the weekly cron that meant waiting up to a week. ## The `rebaseWhen` trap I shipped in #34 Worse than a plain wait, and my fault. `rebaseWhen: "behind-base-branch"` re-pushes any branch whose base has moved, which restarts CI — so the branch goes green ~10 minutes *after* the run that touched it has already passed it by. Observed precisely that: | time | event | |---|---| | 06:38 | `main` moved (#31, #34 merged) | | 07:17–07:23 | Renovate rebased four branches → CI restarted | | 07:34 | run ended, having already evaluated them | | ~07:27+ | CI went green — too late | Repeat weekly and, whenever `main` moves between runs, the safeguard I added to close the stale-base gap **defers the merge it was guarding, potentially forever**. ## Changes **Hourly cron** (`0 * * * *`) — bounds the wait to an hour and largely defuses the starvation. Most runs find nothing to open, because `renovate.json5`'s own `schedule` still restricts PR *creation* to Monday mornings (docker images monthly). Waking up hourly is about **merging**, not creating. **`automergeSchedule: ["at any time"]`** — explicit, so merging never inherits the weekly creation window rather than relying on that staying the default. **`automergeStrategy: "squash"`** — a preference, not part of the fix, and the first draft of this PR wrongly justified it as required. One commit per dependency update on `main` instead of a merge commit plus parent: easier to read, easier to revert, and it matches the squash policy CONTRIBUTING.md documents for non-human contributions. Verified rather than assumed: the repo allows squash (`allow_squash_merge: true`), and Forgejo signs squash commits with the instance key (`repository.signing.MERGES: always`), so `require_signed_commits` on `main` stays satisfied without Renovate holding a signing key. ## Validated Config parses via `json5` with all five keys resolving on the auto-merge rule (`squash`, `at any time`, `platformAutomerge: true`, `3 days`, `behind-base-branch`, types `minor/patch/pin/digest`). Workflow `on:` block still has exactly one cron and `workflow_dispatch` intact. ## Worth watching A full run that opens PRs took **~21 minutes**, and there are two runner slots shared with CI. No-op runs are far cheaper, but if CI starts queuing behind Renovate, dial this to every few hours rather than back to weekly. Once this merges, #33 and #35 should merge on their own within the hour — both are green and up to date. Note they touch `pnpm-lock.yaml`, which is in `cd-apps`' path filter, so each will trigger a production deploy.
ci(renovate): run hourly so auto-merge can actually fire; squash strategy
All checks were successful
CI / Dockerfile Package Check (pull_request) Successful in 26s
CI / Security Scan (pull_request) Successful in 47s
CI / Visual Tests (pull_request) Successful in 3m26s
CI / Checks (pull_request) Successful in 4m40s
CI / Journal Image Smoke Test (pull_request) Successful in 3m30s
CI / E2E Tests (pull_request) Successful in 8m14s
CD Staging / Build & Push Docker Images (pull_request) Has been skipped
CD Staging / Build & Push Docker Images-1 (pull_request) Has been skipped
CD Staging / Deploy Staging (pull_request) Has been skipped
CD Staging / Deploy PR Preview (pull_request) Has been skipped
CD Staging / Tear Down PR Preview (pull_request) Successful in 46s
2a4cd62f9e
Auto-merge was correctly configured but never fired. A debug dry-run settled
why, and it was not what I guessed:

  DEBUG: Branch status green            (branch=renovate/picomatch-…)
  INFO:  DRY-RUN: Would merge PR #33 with strategy "auto"

Nothing was blocked. Renovate was willing to merge the moment it looked. It
simply had no run in which to look while CI was green.

`platformAutomerge` only enqueues at PR *creation*, so for an existing PR
Renovate does the merging itself from inside a run. On the old weekly cron that
meant an eligible PR waited up to a week — and `rebaseWhen:
"behind-base-branch"` made it worse: each run re-pushed any branch whose base
had moved, restarting CI, so the branch went green ~10 minutes *after* the run
that touched it had already passed it by. Observed exactly that on 2026-07-28:
main moved 06:38, branches rebased 07:17-07:23, run ended 07:34, CI green after
Renovate had moved on. Repeat weekly and the safeguard I added in #34 to close
the stale-base gap could defer the merge it was guarding, indefinitely.

Hourly bounds that wait to an hour. Most runs will find nothing to open, since
renovate.json5's own `schedule` still restricts PR *creation* to Monday
mornings (docker base images monthly) — waking up is about merging, not
creating.

Also sets `automergeSchedule: ["at any time"]` explicitly so merging never
inherits the weekly creation window, rather than relying on that remaining the
default.

`automergeStrategy: "squash"` is a preference, not part of the fix, and the
earlier draft of this change wrongly justified it as required. It gives one
commit per dependency update on `main` instead of a merge commit plus parent,
which reads and reverts more cleanly, and matches the squash policy
CONTRIBUTING.md documents for non-human contributions. Checked the repo allows
it (`allow_squash_merge: true`) and that Forgejo signs squash commits with the
instance key (`repository.signing.MERGES: always`), so `require_signed_commits`
on `main` stays satisfied without Renovate holding a signing key.

One thing to watch: a full run that opens PRs took ~21 minutes, and there are
two runner slots shared with CI. A no-op run is far cheaper, but if CI starts
queuing, dial this back to every few hours rather than back to weekly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8hNxgYp777FRqYtVmQNaU
ullrich deleted branch ci/renovate-automerge-strategy-and-hourly 2026-07-29 14:14:49 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
trails-cool/trails!51
No description provided.