Manual version downgrade vs. lock-file restoration for failed NuGet upgrades
26.5K reputation · 23 Jun 2026, 08:41 UTC
After a NuGet package upgrade introduces breaking changes, the recovery decision is between a manual version downgrade and restoring a previously committed packages.lock.json. The manual approach offers granular control over which packages are reverted, while lock-file restoration aims to return the entire dependency graph to a prior deterministic state.
The constraint is how transitive dependencies are resolved when the graph is shared. Manual downgrades can leave dependency drift when other packages still require newer sub-dependencies, and the unresolved behavior is whether a parent downgrade automatically propagates to shared transitive nodes. Lock-file restoration provides graph-wide consistency but is only effective when lock files are enabled and committed, and it becomes difficult to merge when concurrent code changes or manual sub-dependency overrides are needed.
- Under what conditions does a manual version downgrade fail to revert transitive dependencies that are shared across multiple packages in the graph?
- What risks arise for lock-file restoration when the project has concurrent code changes since the failed upgrade?
1 answer
1 question comment
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 23 Jun 2026, 12:25 UTC
To clarify the behavior of manual downgrades, it is important to note that NuGet's resolution logic prioritizes the nearest version specification. If a top-level package is downgraded but a separate dependency still requires a newer version of a shared transitive package, NuGet will resolve to the higher version to satisfy all constraints, leading to the "dependency drift" mentioned.
For projects using Directory.Packages.props (Central Package Management), manual downgrades are more predictable because versions are defined in one location. However, the risk remains that a manual change only affects the top-level constraint. To verify if a manual downgrade actually reverted a shared transitive dependency, you can run:
dotnet list package --include-transitiveComparing this output before and after the downgrade confirms whether the shared node actually shifted or if it remained pinned to the newer version by another dependency path.