From 72e9fb3dd7a4fc85e0ac535c2f0892431c75889e Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Sun, 28 Jun 2026 19:10:58 -0400 Subject: [PATCH] renovate: rebaseWhen=conflicted + split prettier/jshint + ungroup major Actions - rebaseWhen: conflicted (drop :rebaseStalePrs): stop force-rebasing every open branch on each merge to main. Safe because branch protection is strict:false. The old behind-base-branch behavior orphaned manual commits pushed onto Renovate branches and re-ran CI repo-wide on every merge. - Split prettier + jshint out of the eslint 'linting' group: they release independently, so grouping let either gate the other's soak window. - Major GitHub Actions bumps get individual PRs (groupName:null) for independent review; minor/patch stay grouped to cut noise. --- renovate/README.md | 33 ++++++++++++++++++++++++++++----- renovate/default.json | 17 +++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/renovate/README.md b/renovate/README.md index 3295e55..897b1ba 100644 --- a/renovate/README.md +++ b/renovate/README.md @@ -25,8 +25,23 @@ jellyrock//renovate.json The org default carries: -- **Sane base preset** — `config:recommended` + dashboard disabled + - stale-PR rebasing. +- **Sane base preset** — `config:recommended` + dashboard disabled. +- **`rebaseWhen: "conflicted"`** — Renovate rebases a PR branch only when + it actually conflicts with the base, **not** every time `main` moves. + With branch protection set to `strict: false` (branches need not be + up-to-date to merge) there's no protection reason to keep every branch + current, and `behind-base-branch` rebasing (the old `:rebaseStalePrs`) + caused a "rebase storm": each merge force-rebased every open Renovate + branch, re-triggering full CI on each and **orphaning any manual commit + pushed onto those branches** (e.g. a hand-applied major-bump migration). + Tradeoff: an automerge PR can land tested against slightly-stale `main` + (Renovate flags this as not-recommended-with-automerge), but for + independent dep bumps that risk is low and push-triggered CI on `main` + catches it; when you DO want a fresh pre-merge integration test on a + specific PR (a major, say), tick Renovate's **rebase checkbox** to force + a one-off rebase + CI before merging. Do **not** revert to + `behind-base-branch`/`:rebaseStalePrs` unless branch protection becomes + `strict: true`. - **`separateMinorPatch: true`** — distinct PRs per update type so patches can automerge while minors/majors wait for review. - **Digest pinning** for GitHub Actions, Dockerfiles, and @@ -42,9 +57,17 @@ The org default carries: diffs. Enabling this opens a one-time **"Pin dependencies"** PR per repo; that PR's update type is `pin`, which the automerge rule does **not** match, so a human reviews each one. -- **JS lint stack grouping** — `eslint`, `prettier`, `jshint`, plus - glob-matched `@eslint/*`, `eslint-config-*`, `eslint-plugin-*`. - One coordinated PR instead of one-per-plugin. +- **ESLint stack grouping** — `eslint` plus glob-matched `@eslint/*`, + `eslint-config-*`, `eslint-plugin-*`. One coordinated PR instead of + one-per-plugin, because eslint is version-coupled to its plugins. + `prettier` and `jshint` are deliberately **not** in this group — they + release independently, and grouping bundles their soak windows so a + fresh release of one would gate the other (and the eslint group too). +- **GitHub Actions grouping, majors excepted** — routine minor/patch + Action bumps are grouped into one PR to cut noise; **major** Action + bumps get an individual PR each (`groupName: null` override) so each is + reviewed on its own, since a major can change runner requirements or + default behavior a green build won't surface. - **Soak windows** (`minimumReleaseAge`) before automerge: patch/digest 2 days, minor 5 days, major 7 days. The soak catches a yanked or hotfixed release before it lands unattended. diff --git a/renovate/default.json b/renovate/default.json index f171a82..a69cf6b 100644 --- a/renovate/default.json +++ b/renovate/default.json @@ -3,9 +3,9 @@ "extends": [ "config:recommended", ":disableDependencyDashboard", - ":rebaseStalePrs", ":pinAllExceptPeerDependencies" ], + "rebaseWhen": "conflicted", "addLabels": ["dependencies"], "rollbackPrs": true, "ignorePaths": ["**/node_modules/**"], @@ -26,17 +26,21 @@ "schedule": ["before 6am on monday"], "pinDigests": true }, + { + "description": "Major GitHub Actions bumps get their OWN PR (groupName:null overrides the grouping above) so each is reviewed independently — a major can change runner requirements or default behavior that a green build won't surface, and bundling several into one PR muddies that review. Minor/patch stay grouped to cut routine noise. Schedule + digest-pinning still apply (this rule only overrides grouping).", + "matchManagers": ["github-actions"], + "matchUpdateTypes": ["major"], + "groupName": null + }, { "description": "Pin Docker image digests so a stale :latest can't drift into a deprecated runtime. Applies to Dockerfiles and docker-compose files.", "matchManagers": ["docker-compose", "dockerfile"], "pinDigests": true }, { - "description": "Group the ESLint/Prettier JS-tooling ecosystem together so a single PR covers a coordinated upgrade. Glob patterns catch ecosystem packages (eslint-plugin-*, eslint-config-*, @eslint/*) without enumerating every plugin per repo.", + "description": "Group the ESLint ecosystem together so a single PR covers a coordinated upgrade — eslint is version-coupled to its plugins/configs, so they should move atomically. Glob patterns catch ecosystem packages (eslint-plugin-*, eslint-config-*, @eslint/*) without enumerating every plugin per repo. Prettier and jshint are deliberately NOT grouped here (see next rule): they release independently of eslint, so grouping them would let either gate the other's soak window.", "matchPackageNames": [ "eslint", - "prettier", - "jshint", "@eslint/*", "eslint-config-*", "eslint-plugin-*" @@ -44,6 +48,11 @@ "groupName": "linting", "addLabels": ["javascript"] }, + { + "description": "Prettier and jshint release on their own cadence, independent of the eslint stack. Label them (so they read as JS tooling) but do NOT group them — grouping bundles their soak windows, so a fresh release of one holds back the other (and held back the eslint group too, before this split). Each flows on its own soak.", + "matchPackageNames": ["prettier", "jshint"], + "addLabels": ["javascript"] + }, { "description": "Minor updates automerge once CI is green AND the release has soaked 5 days. The PR is created immediately (internalChecksFilter:none) so it's visible and a human can manually merge early if needed (e.g. a hotfix); the renovate/stability-days pending check holds AUTOMERGE until the soak passes. Minor automerge raises the CI bar — the repo's PR-triggered CI must exercise runtime behavior, not just build+lint (a breaking minor can pass a build but fail at runtime). A repo whose CI can't meet that should override this rule to automerge:false. See renovate/README.md.", "matchUpdateTypes": ["minor"],