dependabot PR loop: react 19 broke testing-library, downgrading react re-broke the design system that needed react 19
Breaks a dependabot upgrade loop where react 19 breaks testing-library and downgrading react re-breaks the design system, by upgrading the lagging dependency first and grouping the trio into one PR. Use when a dependency agent flips between two failing states on the same upgrade. Not for single-package upgrade failures, lockfile merge conflicts, or peer-dependency errors unrelated to react.
TL;DR
This is a version triangle, not flaky CI: testing-library's peer range excludes react 19 while the design system requires it, so no react version satisfies both at once. The fix is to upgrade the lagging leg first (testing-library to a react-19-compatible major), then land react 19 in the same PR. Never let the agent alternate between two failing states. Record the constraint set once and solve the triangle.
The query
dependabot PR loop: react 19 broke testing-library, downgrading react re-broke the design system that needed react 19Use this when
- A dependency agent bounces between two upgrades, each one breaking what the other fixed.
- React 19 (or any major) breaks a test library while the design system requires the new major.
- Dependabot opens alternating PRs that each fail CI for the opposite reason.
Not for
- A single upgrade PR that fails once (that is a normal broken bump, not a loop).
- Lockfile merge conflicts between two agent PRs.
- Peer-dependency errors with no version triangle behind them.
Steps
Step 1: Map the constraint triangle on paper
grep -A 5 '"peerDependencies"' node_modules/@testing-library/react/package.json
grep '"react"' packages/design-system/package.jsonExpected output: testing-library declares a peer range like react: ^18 while the design system requires react: ^19. Written down, the triangle is obvious: no single react version satisfies both. The agent must stop optimizing one constraint at a time.
Step 2: Find the testing-library release that supports react 19
npm view @testing-library/react versions --json | tail -5
npm view @testing-library/react@[latest] peerDependenciesExpected output: a testing-library major whose peer range includes react 19. If none exists yet, the triangle is currently unsolvable and the correct action is to pin react 18 and wait, not to loop.
Step 3: Upgrade the lagging leg first, against the old react
npm install -D @testing-library/react@[react-19-compatible]
npm test -- --selectProjects unitExpected output: the new testing-library passes against react 18 (it supports both). This decouples the two upgrades so each step is independently green.
Step 4: Land react 19 together with the already-upgraded testing-library
npm install react@19 react-dom@19
npm testExpected output: the full suite passes because every leg of the triangle now accepts react 19. The design system requirement and the test library peer range agree.
Step 5: Group the trio in dependabot so it never splits again
groups:
react-trio:
patterns: ["react", "react-dom", "@testing-library/*"]Expected output: dependabot opens one PR for the whole triangle instead of three PRs that each break the others. The group update is tested as a unit.
Step 6: Add a CI check that fails on peer-dependency warnings
npm install --dry-run | grep -i "peer dep" && exit 1 || echo "peers clean"Expected output: the check passes when peers are clean and fails the build when a future bump reintroduces a triangle. The loop can never silently restart.
Variant phrasings
renovate kept flip-flopping two PRs that each need the other
Same triangle shape. Group the PRs (renovate groupName) and upgrade the lagging leg first.
agent bumped django then djangorestframework demanded the old django
Same pattern in Python. Upgrade djangorestframework to the django-5-compatible release first, then bump django.
dependabot react 19 PR breaks tests, revert PR breaks the design system
Stop reverting. The revert just restores the other broken state. Solve the triangle forward.
Why it happens
The resolver optimizes one constraint at a time: bump react, testing-library's peers break; revert react, the design system's requirement breaks. Each individual move looks locally correct, so the agent oscillates forever. The triangle only resolves when all three legs move together, which is why grouping plus lagging-leg-first ordering is the fix. Agents loop because they never write down the full constraint set.
Edge cases
- If no testing-library release supports react 19 yet, the honest answer is to pin react 18 and schedule the upgrade. Looping does not create a compatible release.
- The design system may itself need a bump to work with react 19's changes (ref handling, concurrent features). Check its changelog, not just its peer range.
overridesin package.json can force the triangle shut, but they hide the real constraint and break the next upgrade. Prefer the real compatible versions.- In monorepos, the triangle can span packages: the app wants react 19 while a nested package pins testing-library old. Audit all workspaces, not just the root.
- Snapshot tests often encode react-version-specific output. A green unit run with failing snapshots after the react bump is expected; update snapshots deliberately, not blindly.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_N5RCXl2rS2Dc3rrnrgOq5A