ci(renovate): auto-merge minor/patch after a release-age hold #34

Merged
ullrich merged 1 commit from ci/renovate-automerge into main 2026-07-28 06:38:29 +00:00
Owner

Cuts the churn of rubber-stamping dependency PRs. Scope is deliberately narrow: minor, patch, pin, digest only — majors stay manual.

Why this is safe here specifically

The cautionary tale from the chameth.com migration write-up was auto-merges landing before CI reported, so Renovate marked PRs merged and the commits never reached the default branch. Root cause: no branch protection existed.

We have it, and I verified the specifics rather than assuming:

required_checks:        ["CI / *"]
enable_merge_whitelist: false
required_approvals:     0

So the renovate collaborator can merge, but only through the required checks — which cover typecheck, lint, unit tests, e2e, visual regression, container smoke test and secret scan. Renovate cannot vote itself in.

Three safeguards beyond CI

minimumReleaseAge: "3 days" — the one failure mode CI genuinely cannot catch is a release yanked or hotfixed hours after publication. A version must be public for three days before Renovate will open, and therefore merge, it.

rebaseWhen: "behind-base-branch" — there's no merge queue (#25), so a PR is normally tested against the base it was pushed on, not the base it lands on. Rebasing when main moves closes that gap for Renovate's PRs: CI re-runs against current main before auto-merge fires.

This is the targeted alternative to flipping block_on_outdated_branch on the protection rule (currently false), which would give the same guarantee but force every human PR to rebase whenever main moves. Worth considering separately — with ~10 min CI that's a real tax on stacked work, so I left it alone.

platformAutomerge: true — prefer the forge's "merge when checks succeed" over Renovate merging by hand. If Forgejo's platform doesn't implement it, Renovate falls back to merging itself once green; branch protection gates it either way.

Two exclusions, even for patches

Placed after the auto-merge rule, since later rules win in Renovate:

  • expo — CI never compiles native code, so a green PR proves only the JS side. Any SDK movement needs an EAS build before landing. Auto-merging would ship unverified native changes, precisely what the existing expo install --fix rule exists to prevent.
  • @fission-ai/openspec — its postUpgradeTask regenerates committed agent skills and slash commands; a generated-file diff deserves eyes.

The already-disabled Expo family (expo-*, @expo/*, react-native*) is unaffected — it produces no PRs at all.

Validation

Parsed the config with json5 and computed the effective automerge value per package, applying Renovate's later-wins ordering, rather than eyeballing the rules:

package update auto-merges
react patch / minor yes
react major no
vite minor yes
@sentry/node minor yes
node digest yes
expo patch / minor no
@fission-ai/openspec patch no
expo-router patch n/a — updates disabled

What to watch on the first run

Renovate runs weekly (Monday before 6am) or on dispatch. Two things worth checking the first time it auto-merges:

  1. that the merge commit is instance-signedrequire_signed_commits is on, and Forgejo creates the merge commit, so it should be, but this is the first time a non-human will exercise that path;
  2. that platformAutomerge is actually honoured on Forgejo rather than silently falling back — visible in the run log.

The workflow's dry_run input defaults to true, so a dispatch is a safe way to preview the effect before Monday.

Cuts the churn of rubber-stamping dependency PRs. Scope is deliberately narrow: **minor, patch, pin, digest only** — majors stay manual. ## Why this is safe here specifically The cautionary tale from the chameth.com migration write-up was auto-merges landing **before CI reported**, so Renovate marked PRs merged and the commits never reached the default branch. Root cause: no branch protection existed. We have it, and I verified the specifics rather than assuming: ``` required_checks: ["CI / *"] enable_merge_whitelist: false required_approvals: 0 ``` So the `renovate` collaborator *can* merge, but only through the required checks — which cover typecheck, lint, unit tests, e2e, visual regression, container smoke test and secret scan. Renovate cannot vote itself in. ## Three safeguards beyond CI **`minimumReleaseAge: "3 days"`** — the one failure mode CI genuinely cannot catch is a release yanked or hotfixed hours after publication. A version must be public for three days before Renovate will open, and therefore merge, it. **`rebaseWhen: "behind-base-branch"`** — there's no merge queue (#25), so a PR is normally tested against the base it was *pushed* on, not the base it *lands* on. Rebasing when `main` moves closes that gap for Renovate's PRs: CI re-runs against current `main` before auto-merge fires. This is the targeted alternative to flipping `block_on_outdated_branch` on the protection rule (currently `false`), which would give the same guarantee but force **every human PR** to rebase whenever `main` moves. Worth considering separately — with ~10 min CI that's a real tax on stacked work, so I left it alone. **`platformAutomerge: true`** — prefer the forge's "merge when checks succeed" over Renovate merging by hand. If Forgejo's platform doesn't implement it, Renovate falls back to merging itself once green; branch protection gates it either way. ## Two exclusions, even for patches Placed *after* the auto-merge rule, since later rules win in Renovate: - **`expo`** — CI never compiles native code, so a green PR proves only the JS side. Any SDK movement needs an EAS build before landing. Auto-merging would ship unverified native changes, precisely what the existing `expo install --fix` rule exists to prevent. - **`@fission-ai/openspec`** — its `postUpgradeTask` regenerates committed agent skills and slash commands; a generated-file diff deserves eyes. The already-disabled Expo family (`expo-*`, `@expo/*`, `react-native*`) is unaffected — it produces no PRs at all. ## Validation Parsed the config with `json5` and computed the **effective** `automerge` value per package, applying Renovate's later-wins ordering, rather than eyeballing the rules: | package | update | auto-merges | |---|---|---| | `react` | patch / minor | **yes** | | `react` | major | no | | `vite` | minor | **yes** | | `@sentry/node` | minor | **yes** | | `node` | digest | **yes** | | `expo` | patch / minor | no | | `@fission-ai/openspec` | patch | no | | `expo-router` | patch | n/a — updates disabled | ## What to watch on the first run Renovate runs weekly (Monday before 6am) or on dispatch. Two things worth checking the first time it auto-merges: 1. that the merge commit is **instance-signed** — `require_signed_commits` is on, and Forgejo creates the merge commit, so it should be, but this is the first time a non-human will exercise that path; 2. that `platformAutomerge` is actually honoured on Forgejo rather than silently falling back — visible in the run log. The workflow's `dry_run` input defaults to `true`, so a dispatch is a safe way to preview the effect before Monday.
ci(renovate): auto-merge minor/patch after a release-age hold
All checks were successful
CI / Security Scan (pull_request) Successful in 1m21s
CI / Dockerfile Package Check (pull_request) Successful in 29s
CI / Checks (pull_request) Successful in 2m54s
CI / Visual Tests (pull_request) Successful in 3m9s
CI / Journal Image Smoke Test (pull_request) Successful in 4m3s
CI / E2E Tests (pull_request) Successful in 18m58s
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 34s
d74ece0bee
Cuts the manual churn of rubber-stamping dependency PRs. Scope is deliberately
narrow: minor, patch, pin and digest only. Majors stay manual.

This is safe here for a reason that was NOT true where the same setup went
wrong elsewhere: `main` is protected with `CI / *` required, covering typecheck,
lint, unit tests, e2e, visual regression, a container smoke test and a secret
scan. Renovate cannot vote itself in — a merge whose checks have not passed is
refused by the forge. The cautionary tale (chameth.com's Forgejo migration) was
auto-merges landing *before* CI reported because no protection rule existed, so
commits silently never reached the default branch. Verified our side: protection
on `main` has `enable_merge_whitelist: false` and `required_approvals: 0`, so
the `renovate` collaborator can merge, but only through the required checks.

Three safeguards beyond CI:

- `minimumReleaseAge: "3 days"`. The failure CI cannot catch is a release that
  is yanked or hotfixed hours after publication. A version must be public for
  three days before Renovate will open, and therefore merge, it.
- `rebaseWhen: "behind-base-branch"`. There is no merge queue (#25), so a PR is
  normally tested against the base it was pushed on rather than the base it
  lands on. Rebasing when `main` moves closes that gap for Renovate's own PRs:
  CI re-runs against current `main` before auto-merge fires. This is the
  targeted alternative to `block_on_outdated_branch` on the protection rule,
  which would also force every human PR to rebase.
- `platformAutomerge: true`, preferring the forge's own "merge when checks
  succeed" over Renovate merging by hand. If the Forgejo platform does not
  implement it, Renovate falls back to merging itself once green — still gated
  by branch protection either way.

Two packages are excluded even for patches, and the rule sits after the
auto-merge rule because later rules win in Renovate:

- `expo` — CI never compiles native code, so a green PR proves only the JS side.
  Any SDK movement needs an EAS build before it lands. Auto-merging would ship
  unverified native changes, which is exactly what the existing
  `expo install --fix` rule was written to avoid.
- `@fission-ai/openspec` — its postUpgradeTask regenerates committed agent
  skills and slash commands; a generated-file diff deserves review.

The already-disabled Expo family (`expo-*`, `@expo/*`, `react-native*`) is
unaffected: it produces no PRs at all.

Validated by parsing the config with json5 and computing the effective
`automerge` value per package, applying Renovate's later-wins ordering:

  react @ patch/minor      -> true      react @ major          -> false
  vite @ minor             -> true      @sentry/node @ minor   -> true
  node @ digest            -> true      expo @ patch/minor     -> false
  @fission-ai/openspec     -> false     expo-router @ patch    -> n/a (disabled)

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 2026-07-28 06:38:29 +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!34
No description provided.