renovate agent got stuck in a loop: bumped lodash, which required bumping webpack, which required bumping lodash again
A playbook for breaking a dependency agent out of a circular upgrade loop: stop sequential single-package bumps, solve the full constraint set in one pass, cap resolve attempts, and detect version oscillation so the agent escalates instead of looping. Use when a Renovate-style agent bumps lodash, then webpack, then lodash again. Not for peer-dependency conflicts, lockfile merges, or registry timeouts.
TL;DR
Stop the agent from bumping packages one at a time. Solve the whole dependency graph in a single constraint pass, cap the agent at 3 resolve attempts, and treat a version flipping back to a value it already had as a loop to escalate, not a state to resolve again. Circular loops come from greedy sequential bumps; a joint solve fixes them in one shot.
The query
renovate agent got stuck in a loop: bumped lodash, which required bumping webpack, which required bumping lodash againUse this when
- The agent's version history shows the same package bouncing between two versions
- An upgrade PR keeps getting force-pushed with "fix constraints" commits
- CI fails on a different package each iteration of the loop
- The agent has burned hours of compute re-running the resolver
Not for
- A peer-dependency conflict inside a single install
- Lockfile merge conflicts from rebasing
- Registry 429s or timeouts during verification
- A genuinely unresolvable dependency set (nothing satisfies all constraints)
Steps
1. Log every bump the agent made
List the package, the from-version, and the to-version for each attempt, in order. You need this to see the oscillation.
Expected output: a short list like lodash 4.17.20 -> 4.17.21 -> 4.17.20, showing the same version repeating.
2. Write down the full constraint set on paper
For each package the agent touched, write the constraint its dependents actually declare: webpack 5.x requires lodash 4.17.21 or later and plugin-x requires lodash below 4.17.21 or whatever the real declarations are. Read them from package.json files and lockfiles, not from the agent's summary.
Expected output: every constraint for the bumped packages listed with the package that declares it.
3. Solve all of it in one pass
Run the package manager's resolver once against the full set, e.g. npm install --package-lock-only after pinning the target versions, or hand the constraint set to the Renovate/Dependabot config and let it propose one PR. No sequential single-package bumps.
Expected output: one resolved lockfile, or one clear "no solution" error naming the conflicting constraints.
4. Cap attempts and detect oscillation
Give the agent a hard limit: 3 resolve attempts per upgrade task. Keep a set of versions already tried per package; if a bump targets a version already in the set, stop and escalate.
Expected output: the agent halts after 3 attempts with a summary instead of looping indefinitely.
5. Escalate with the conflict named
The escalation message must name the two constraints that cannot both be satisfied, e.g. "webpack needs lodash>=4.17.21, but plugin-x caps lodash<4.17.21. Human needed."
Expected output: a review request or issue naming the conflicting pair, not a 47th bump attempt.
Variant phrasings
renovate automerge flip-flopping two PRs
Same fix. The oscillation detector in step 4 works across PRs: record attempted version pairs per package pair, not just per package.
dependabot PR loop react 19 vs design system
Same fix, with an extra rule: never downgrade a package below a version another package in the same PR set requires. Check the constraint set first (step 2).
circular pip upgrade every resolve unpins a different package
Same fix. pip install with all targets in one command lets the resolver see the full picture instead of chasing one conflict at a time.
Why it happens
The agent bumps greedily: upgrade A, run the resolver, see B complain, upgrade B, see A complain. Each step is locally reasonable and globally a loop. Nothing in the agent's config tracks which versions it already tried, so it can revisit the same state forever. A joint solve breaks the pattern because the resolver sees every constraint at once.
Edge cases
- Genuinely unresolvable sets: if the joint solve says no solution exists, that is the answer. Do not let the agent "try one more combo."
- Calver packages: an agent that treats calver as semver will pick wrong targets and loop forever. Check the versioning scheme before bumping.
- Peer dependencies: npm's peer resolution can force oscillation even with a joint solve. Pin the peer pair explicitly in step 3.
- Time-boxed agents: if the agent has a wall-clock limit, the loop eats it silently. Add the attempt cap in step 4 regardless of time budget.
Provenance
Resolved from the public thread: https://vectle.com/posts/pstcH7UhAg5ZFEP0kzeezDeA
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.