Add an explicit line for version conflicts to any upgrade estimate that touches shared libraries, and check for them in a spike before committing. A conflict, where two libraries need incompatible versions of a third, is the most common reason a planned upgrade overruns, and it often depends on timing outside your control.
What the conflict looks like
Google's chapter on dependency management calls the canonical case the diamond dependency problem. A library at the bottom, call it libbase, is used by two middle libraries, liba and libb. Your application uses both. If liba needs one major version of libbase and libb needs another, and your build can only hold one, you have a conflict.
The chapter notes that languages tolerate this differently. Some can embed isolated copies of different versions in one build. Others cannot. It also says that if you hit a conflict, the easy answer is to skip forward or backward in versions to find something compatible, and when that fails, you resort to locally patching the dependency.
Why it breaks estimates
The effort is not in your code. It is in waiting for, or working around, someone else's release. A neat two-day upgrade turns into a three-week blocked ticket because a plugin has not published support for the new version.
Spot conflicts before you commit
- Read the dependency tree. Use your package manager's tree or why command to see which libraries pull in the package you are upgrading.
- Check each parent's constraints. What version range does each parent allow?
- Check parent release status. Does the newest release of each parent support the target version? Look at release notes and issue trackers.
- Try a dry run. Resolve the new set on a branch and read the resolver's complaint. Our post on previewing an upgrade with a pip dry run shows the idea for one ecosystem.
Resolution options, in order of cost
| Option | When it fits | Cost |
|---|---|---|
| Move parents to versions that agree | A compatible set exists | Low to medium |
| Wait for a parent release | Fix is announced and near | Calendar delay |
| Override or pin the shared version | Parents work in practice at the new version | Needs testing, document it |
| Fork or patch locally | No compatible release will arrive | High, ongoing |
| Replace a parent | A parent is unmaintained | High, but ends the problem |
For override mechanics in specific ecosystems, see our guides on Go and Gradle. The wider decision is covered in what to do when a transitive dependency cannot upgrade.
How to budget
- Add a contingency line labelled "version conflicts" to any estimate that touches a library other libraries depend on.
- Size it from past upgrades. If one in three upgrades hit a conflict, say so in the plan.
- Name a decision date. If a blocked parent has not released by then, switch to the next option.
- Track blocked time separately from working time, so the retro can see how much was waiting.
Communicate the risk
Tell stakeholders that the upgrade has a dependency on a release outside the team. A plan that says "done by week four unless a plugin lags" is more honest, and more useful, than one that promises week four flat.
Where Safeguard fits
Safeguard's SBOM and dependency scanning shows the full tree of direct and transitive components, which makes conflicts easier to see before they stall a ticket. A free scan is at gold.safeguard.sh/scan.
Quick check
Is a version conflict the same as a vulnerability?
No. A conflict is about incompatible version requirements. A vulnerability is a flaw. They meet when the fixed version is one your parents do not yet allow.
Should I always fork when a parent lags?
No. Forking adds ongoing maintenance. Try compatible parent versions, a tested override, or a replacement first.
Sources
Last reviewed: 19 October 2026.