From cbed0c87a99c6918b246cf99c64c19649fae4cea Mon Sep 17 00:00:00 2001 From: Eric J Date: Wed, 29 Jul 2026 22:43:37 -0700 Subject: [PATCH 1/2] chore(renovate): tune for "Automated PRs" - security bypass, burst caps, lockfile hold The Renovate GitHub App went live with "Automated PRs", "Require config file" and "Create onboarding PRs", so the config now has to hold up unattended rather than describe an intent. - `timezone: America/Los_Angeles`. The schedule was UTC, so "before 6am on monday" actually fired around midnight Sunday local. - `prConcurrentLimit` / `prHourlyLimit`. The first run after enabling sees every pending update at once, and each PR now costs a ~4 minute CI run. - `vulnerabilityAlerts` bypasses the weekly window - a CVE fix could otherwise sit ~10 days (up to 3 for the age floor, then up to 7 for Monday). Its `minimumReleaseAge` is **25 hours, not 0**: pnpm 11 refuses anything younger than 24h without writing a `minimumReleaseAgeExclude:` bypass into pnpm-workspace.yaml, so a same-day security PR would reintroduce exactly the hazard this config exists to prevent. - `osvVulnerabilityAlerts` - GitHub's advisories miss ecosystem-only reports. - `lockFileMaintenance` pinned off. It is automated `pnpm up` for the lockfile, and transitive re-resolution is what triggered the `TS2321: Excessive stack depth ... UserConfig` pathology in vite.config.ts while installing the same target versions directly did not. Off by default; pinned so it cannot get switched on as tidying. - `dependencyDashboardApproval` deliberately NOT set - it would re-impose scan-only behaviour at the config layer and defeat the app's own setting. CLAUDE.md's "Renovate is inert until the GitHub App is enabled" is now false and is corrected, along with the two settings whose reasoning is not guessable from the outside. Validated with `renovate-config-validator` (exit 0). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54 --- .github/renovate.json5 | 59 +++++++++++++++++++++++++++++++++++++++--- CLAUDE.md | 24 ++++++++++++++--- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c62d5a05..aeffd7ab 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -5,9 +5,19 @@ // re-proposing, every week, the exact bump this project has already decided // against - and to make the reason travel with the proposal when there is one. // -// This file does nothing on its own. Renovate only acts once the Renovate -// GitHub App is enabled for the repository (https://github.com/apps/renovate). -// Validated with `renovate-config-validator` before landing. +// The Renovate GitHub App was enabled on 2026-07-30 with "Automated PRs", +// "Require config file" and "Create onboarding PRs". Two consequences worth +// knowing: "Require config file" means Renovate does NOTHING, silently, if this +// file is absent or unparseable from the DEFAULT branch - so validate edits, and +// never let a broken config read as "no updates available". And onboarding PRs +// are moot while this file exists, since onboarding only fires with no config. +// Validate any edit with `renovate-config-validator` (run it from OUTSIDE the +// project root - a bare `npx` here fails with `EBADDEVENGINES`). +// +// `dependencyDashboardApproval` is deliberately NOT set. It would require ticking +// a box per update before a PR appears, which re-imposes scan-only behaviour at +// the config layer and would quietly defeat the app's "Automated PRs" setting. +// PRs are wanted; what is not wanted is anything MERGING unread - see `automerge`. // // THE pnpm INTERACTION, which is the subtle part. // This workspace relies on pnpm 11's built-in release-age cooldown - there is no @@ -44,9 +54,50 @@ dependencyDashboard: true, - // One weekly batch instead of a trickle. Times are UTC (no `timezone` set). + // Seattle. Without this the schedule below is UTC, i.e. "before 6am Monday" + // fires around midnight Sunday local - which reads as a bot that runs at + // random times. + timezone: "America/Los_Angeles", + + // One weekly batch instead of a trickle. To pull a run forward without editing + // this, tick the "Check this box to trigger a request for Renovate to run + // again" checkbox on the dependency dashboard issue. schedule: ["before 6am on monday"], + // Now that PRs actually arrive unattended, cap the burst. The first run after + // enabling the app sees every pending update at once; without a limit that is + // a dozen PRs in one morning, each triggering a ~4 minute CI run. + prConcurrentLimit: 3, + prHourlyLimit: 2, + + // Renovate's `lockFileMaintenance` is automated `pnpm up` for the lockfile, and + // that is the one dependency operation this repo has measured as harmful: + // re-resolving the ~22 surrounding transitive packages triggered + // `TS2321: Excessive stack depth comparing types ... 'UserConfig'` in + // vite.config.ts, while installing the SAME target versions directly did not. + // CLAUDE.md's "prefer targeted `pnpm add` over `pnpm up`" is exactly this. + // Off by default; pinned off explicitly so it cannot get switched on as tidying. + lockFileMaintenance: { enabled: false }, + + // Security fixes must not wait for the weekly window. Without this block a CVE + // fix could sit ~10 days: up to 3 for the age floor, then up to 7 for Monday. + // + // NOTE THE AGE VALUE, which is not zero and must not be. pnpm 11 refuses + // anything younger than 24h without writing a `minimumReleaseAgeExclude:` + // bypass into pnpm-workspace.yaml, so a same-day security PR would reintroduce + // precisely the hazard the top-of-file comment exists to prevent. 25 hours + // clears pnpm's floor with an hour to spare and drops 3 days to ~1. + vulnerabilityAlerts: { + schedule: [], + minimumReleaseAge: "25 hours", + prBodyNotes: [ + "Security update - this bypassed the weekly schedule deliberately. Still read it: `brace-expansion`'s advisory carries a flat `<=5.0.7` range, so `pnpm audit` flags a version that already has the fix. \"The audit is red\" is not by itself a reason to take a bump here.", + ], + }, + + // GitHub's own advisories miss ecosystem-only reports; OSV catches more. + osvVulnerabilityAlerts: true, + packageRules: [ { // THE ONE THAT MATTERS MOST. Without it a bot proposes 6.0.3 -> 7.x every diff --git a/CLAUDE.md b/CLAUDE.md index 51369e4c..3c8dd8a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -214,9 +214,27 @@ config, above pnpm's default, so Renovate can never propose a release pnpm would want a bypass for. If `minimumReleaseAgeExclude:` appears in a bot PR's diff, that PR is wrong; fix the age rule, don't commit the bypass. -Renovate is inert until the GitHub App is enabled on the repo. Validate any edit -with `renovate-config-validator` (run it from outside the project root - a bare -`npx` here fails with `EBADDEVENGINES`). +**The app is live as of 2026-07-30** - enabled with "Automated PRs", "Require +config file" and "Create onboarding PRs". So Renovate opens real PRs on its own +now; `automerge: false` is what keeps anything from *landing* unread, and +`dependencyDashboardApproval` is deliberately unset because it would re-impose +scan-only behaviour at the config layer and defeat the app setting. + +"Require config file" is the one with teeth: **a config that fails to parse makes +Renovate do nothing at all, silently**, which is indistinguishable from "no +updates available". Validate any edit with `renovate-config-validator` (run it +from outside the project root - a bare `npx` here fails with `EBADDEVENGINES`). + +Two settings whose reasoning is not guessable from the outside: + +- **`lockFileMaintenance` is pinned off.** It is automated `pnpm up` for the + lockfile - the one dependency operation measured as harmful here, since + transitive re-resolution is what triggered the `TS2321` pathology below. +- **`vulnerabilityAlerts.minimumReleaseAge` is `"25 hours"`, not `0`.** Security + fixes skip the weekly window, but they cannot skip pnpm's 24-hour floor: a + same-day PR would make pnpm write the `minimumReleaseAgeExclude:` bypass this + whole section exists to prevent. 25 hours clears pnpm and still drops the wait + from 3 days to ~1. Branch protection on `main` is **not** configured. It is the natural follow-up now that a check exists, but it is a repository setting rather than a file. From b76c86bf0257a543c7983d4518cdf33464d41ebd Mon Sep 17 00:00:00 2001 From: Eric J Date: Wed, 29 Jul 2026 22:46:13 -0700 Subject: [PATCH 2/2] style: apply vp check --fix to the renovate config and CLAUDE.md Caught by the CI workflow from #56 on its first real use - `vp check` (no --fix) failed in 27s on two formatting issues. Worth noting the gate earned its keep immediately on a comment-only change. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54 --- .github/renovate.json5 | 2 +- CLAUDE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index aeffd7ab..cbfbd83f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -91,7 +91,7 @@ schedule: [], minimumReleaseAge: "25 hours", prBodyNotes: [ - "Security update - this bypassed the weekly schedule deliberately. Still read it: `brace-expansion`'s advisory carries a flat `<=5.0.7` range, so `pnpm audit` flags a version that already has the fix. \"The audit is red\" is not by itself a reason to take a bump here.", + 'Security update - this bypassed the weekly schedule deliberately. Still read it: `brace-expansion`\'s advisory carries a flat `<=5.0.7` range, so `pnpm audit` flags a version that already has the fix. "The audit is red" is not by itself a reason to take a bump here.', ], }, diff --git a/CLAUDE.md b/CLAUDE.md index 3c8dd8a8..1610a31b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -216,7 +216,7 @@ that PR is wrong; fix the age rule, don't commit the bypass. **The app is live as of 2026-07-30** - enabled with "Automated PRs", "Require config file" and "Create onboarding PRs". So Renovate opens real PRs on its own -now; `automerge: false` is what keeps anything from *landing* unread, and +now; `automerge: false` is what keeps anything from _landing_ unread, and `dependencyDashboardApproval` is deliberately unset because it would re-impose scan-only behaviour at the config layer and defeat the app setting.