Skip to content
Fran Gonzalez
← Back to blog
(updated Jul 16, 2026)·Clanker·7 min read

Reducing Renovate's blast radius: split groups, custom managers, and the temporal-polyfill lesson

A single broken dependency blocked 20 updates in one Renovate PR. Three config changes prevent it from happening again.

Some matmuls wrote this slop, sorry. My goal with this content is to document some work I (a real human bean) do while poking the Clanker, and try to learn something along the way.

A single broken dependency blocked 20 unrelated updates in one Renovate PR, and three config changes prevent it from happening again.

Failure mode

Renovate PR #30 grouped every non-major dependency update into one branch: @radix-ui/*, @schedule-x/*, @tauri-apps/*, clap, tokio, toml, tower-http, lucide-react, react-router-dom, and a dozen more. Twenty packages, one PR, one lockfile regeneration step.

The lockfile regeneration failed. @schedule-x/calendar@4.6.0 declares an exact peer dependency: temporal-polyfill: 0.3.0. An exact pin. The repo had temporal-polyfill@0.3.2. Under strictPeerDependencies: true in pnpm-workspace.yaml, pnpm aborted the install with ERR_PNPM_PEER_DEP_ISSUES. Renovate couldn’t update pnpm-lock.yaml.

CI then failed with a different error: ERR_PNPM_OUTDATED_LOCKFILE. The package.json had been bumped but the lockfile hadn’t, so pnpm install --frozen-lockfile rejected the mismatch. The entire PR was un-mergeable. Nineteen unrelated updates waited on one peer conflict.

The Renovate dependency dashboard showed a pending temporal-polyfill-1.x major bump. Even after fixing the 0.3.0 conflict, that major would re-break the peer: @schedule-x/calendar still pins 0.3.0 in its latest release. The conflict would recur.

Note

Rebasing this PR onto the corrected config was a hard conflict. Renovate force-pushed a regenerated commit that preserved only four of the original twenty bumps. The remaining sixteen (@radix-ui/*, @tauri-apps/api, and the Cargo workspace deps) were dropped and will now arrive as separate group PRs, which is the intended behavior after the split-group config.

Configuration changes

Four changes to renovate.json and package.json addressed the immediate failure and the structural problem behind it.

1. Downgrade to match the exact peer pin

// apps/desktop/ui/package.json
- "temporal-polyfill": "0.3.2"
+ "temporal-polyfill": "0.3.0"

@schedule-x/calendar@4.6.0 (the latest version) pins temporal-polyfill: 0.3.0 as an exact peer. There is no newer release that loosens this. Downgrading to 0.3.0 matches the peer exactly and unblocks lockfile regeneration.

I considered two alternatives. Disabling strictPeerDependencies would keep 0.3.2 but reverse the supply-chain hardening from the mise and pnpm post: every future peer mismatch would silently pass. packageExtensions can add missing peers but cannot loosen an existing exact pin. Downgrading was the only fix that preserved the hardening.

2. Split the monolith group into focused groups

The original config grouped every minor and patch update into one "non-major updates" PR:

// renovate.json — before
{
  "description": "Group non-major updates",
  "matchUpdateTypes": ["minor", "patch"],
  "groupName": "non-major updates",
}

I kept this as a catch-all but added specific group rules after it. In Renovate, later packageRules override earlier ones for the same field, so the specific groups win for their packages:

// renovate.json — after
{
  "description": "Catch-all: group remaining minor/patch updates",
  "matchUpdateTypes": ["minor", "patch"],
  "groupName": "non-major updates"
},
{
  "description": "Group @schedule-x calendar ecosystem (tight peer-dep coupling)",
  "matchPackagePatterns": ["^@schedule-x/"],
  "groupName": "schedule-x"
},
{
  "description": "Group Radix UI primitives",
  "matchPackagePatterns": ["^@radix-ui/"],
  "groupName": "radix-ui"
},
{
  "description": "Group Tauri packages (npm + cargo)",
  "matchPackagePatterns": ["^@tauri-apps/", "^tauri"],
  "groupName": "tauri"
},
{
  "description": "Group Rust workspace direct deps",
  "matchManagers": ["cargo"],
  "groupName": "rust workspace deps"
}

Each group is independently reviewable and mergeable. A peer conflict in @schedule-x/* no longer blocks @radix-ui/* or the Cargo workspace deps. The catch-all handles everything that doesn’t match a specific group.

3. Sync the Rust toolchain across three files with customManagers

The Rust toolchain version lives in three places: mise.toml (the source of truth, currently 1.96.0), .github/workflows/test.yml (the toolchain: input to dtolnay/rust-toolchain, also 1.96.0), and the Dockerfile (FROM rust:1.94.1-slim-bookworm, two patches behind). Renovate’s built-in mise manager bumps mise.toml. The dockerfile manager bumps the FROM line. But the toolchain: 1.96.0 literal in the workflow had no manager: it was bumped manually or not at all.

A customManagers regex teaches Renovate to find and bump that literal:

// renovate.json
"customManagers": [
  {
    "customType": "regex",
    "description": "Bump Rust toolchain pin in CI workflows alongside mise.toml and Dockerfile",
    "managerFilePatterns": ["/^\\.github\\/workflows\\/.+\\.ya?ml$/"],
    "matchStrings": [
      "toolchain:\\s*(?<currentValue>\\d+\\.\\d+\\.\\d+)"
    ],
    "depNameTemplate": "rust",
    "datasourceTemplate": "rust-version"
  }
]

A grouping rule puts all three sources (mise.toml, Dockerfile, workflow regex) into one rust toolchain PR so they land in lockstep:

{
  "description": "Group all Rust toolchain pins (mise.toml, Dockerfile, workflow) so they land in lockstep",
  "matchDepNames": ["rust"],
  "groupName": "rust toolchain",
}

The rust-version datasource queries Rust releases. The regex matches toolchain: 1.96.0 in any workflow YAML file. With rangeStrategy: "pin", Renovate writes the exact version back.

4. Hold major bumps on tightly-coupled deps

Renovate’s dashboard showed a pending temporal-polyfill-1.x major bump. @schedule-x/calendar still pins the peer at 0.3.0, so any major would re-break the peer resolution. A packageRule disables major bumps until upstream loosens the peer:

{
  "description": "Hold temporal-polyfill major; @schedule-x/calendar pins peer at 0.3.0 exactly and any major breaks the peer resolution",
  "matchPackageNames": ["temporal-polyfill"],
  "matchUpdateTypes": ["major"],
  "enabled": false,
}

Patch and minor updates within 0.x still go through. The rule is removed once @schedule-x/calendar ships a version with a loosened peer.

Outcome

After the changes, pnpm install --frozen-lockfile passes. This was the exact CI command that was failing. The lockfile regenerates cleanly with temporal-polyfill@0.3.0 matching the peer pin.

Rebasing the original PR onto the corrected config was a hard conflict. Renovate force-pushed a regenerated commit that only preserved four of the original twenty bumps (@tanstack/react-query, lucide-react, react-router-dom, and temporal-polyfill). The remaining sixteen (@radix-ui/*, @tauri-apps/api, clap, tokio, toml, tower-http, and others) were dropped during the rebase. This confirms that the split-group config works as designed. Those sixteen deps will now arrive as separate, independently-mergeable radix-ui, tauri, and rust workspace deps group PRs instead of being stuck in the monolith.

The next Renovate run will produce separate group PRs: schedule-x, radix-ui, tauri, rust workspace deps, mise toolchain, rust toolchain, dev dependencies, plus a catch-all non-major updates. One broken dep no longer blocks the batch.

The customManagers regex and rust toolchain grouping rule are forward-looking: the Dockerfile is still at 1.94.1 (two patches behind mise.toml and the workflow), but all three Rust pins will land in lockstep on the next Renovate cycle.

Remaining risk

I would have split the groups from the start, not after a monolith failure. The catch-all "non-major updates" group was the default from config:recommended, and I left it because it worked, until it didn’t. The blast radius of a single dependency conflict scales with the number of deps in the group. Smaller groups cap that radius by design.

The temporal-polyfill major hold is a stopgap. The real fix is @schedule-x/calendar loosening the peer from 0.3.0 to ^0.3.0 or broader. Until then, the hold rule prevents Renovate from proposing a major that would re-break the peer.

References

This post was written with AI assistance.