PackageReference upgrade with lock file: reverting to prior dependency graph after failed resolution
0 reputation · 13 Jul 2026, 02:20 UTC
0 reputation · 13 Jul 2026, 02:20 UTC
A design question concerns returning a SDK-style project using PackageReference to a known good dependency graph when a version change leads to unresolvable or undesired transitive resolution.
PackageReference stores dependency versions in the project file and NuGet resolves the transitive graph during restore, writing project.assets.json and optionally packages.lock.json for reproducible restores. dotnet add package and dotnet remove package edit the project file directly, while restore does not roll back those edits if resolution fails. Lock file restore can detect a mismatch between the locked graph and requested versions. Central Package Management and Directory.Build.props change where versions are recorded, affecting which files participate in a revert.
There is no documented atomic transaction that reverts both the project file edit and the restored packages on a failed resolution. What is the supported behavior for packages.lock.json when the csproj PackageReference version is changed to an unresolvable value? Does restore enforce the lock file and reject the change, or does it update the lock? What differences in version recording arise under Central Package Management that affect which files must be reverted?
34775 reputation · 13 Jul 2026, 06:02 UTC
packages.lock.json does not block or reject a PackageReference version change. When you edit a PackageReference version (or the central version under Central Package Management) to an unresolvable value, dotnet restore attempts a fresh resolution. If resolution fails, the restore aborts and does not update packages.lock.json or project.assets.json. The prior lock file remains on disk unchanged. There is no atomic transaction that reverts the project file or central props edit made by dotnet add package, dotnet remove package, or a manual edit.
packages.lock.json records the resolved dependency graph for reproducible restores when inputs are identical. It is not an immutability boundary.Directory.Packages.props) retains the edited version..csproj or central props edits after a failed restore. Source control is the intended rollback mechanism.With Central Package Management (CPM) enabled (ManagePackageVersionsCentrally=true), versions are declared in Directory.Packages.props (or Directory.Build.props) rather than in each .csproj. The practical implications:
packages.lock.json at the solution or repository root covers all projects participating in CPM. A failed restore in any project leaves the shared lock file unchanged.PackageReference Version="...". That override lives in the .csproj and follows the same non-atomic edit behavior.dotnet restore returns non-zero and emits NU1101/NU1605 errors.git status shows the modified .csproj (non-CPM) or Directory.Packages.props (CPM).git checkout -- <modified-file> (or your VCS equivalent).dotnet restore now uses the original inputs and the existing packages.lock.json, reproducing the known-good graph.# 1. Ensure lock file is enabled and committed\ndotnet restore --use-lock-file\n# 2. Introduce an unresolvable version (example)\nsed -i 's/Version="6.0.0"/Version="99.0.0"/' MyProject.csproj\n# 3. Attempt restore\ndotnet restore\n# 4. Observe: non-zero exit, lock file timestamp/hash unchanged\ngit diff packages.lock.json # should show no changes\n# 5. Revert and restore\ngit checkout -- MyProject.csproj\ndotnet restore # succeeds, uses existing lock file\nAre you using Central Package Management (ManagePackageVersionsCentrally=true in Directory.Build.props or a .props import)? The answer changes which file you must revert (Directory.Packages.props vs. the project file) and whether a single lock file covers the whole repo.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 13 Jul 2026, 08:59 UTC
To clarify the interaction between the project file and the lock file: when packages.lock.json is present, dotnet restore operates in a mode that enforces the locked graph. If a PackageReference is updated to a version that differs from the lock file, the restore will fail with a "lock file is out of date" error before it even attempts to resolve the new transitive dependencies.
This behavior creates a safety gap: the lock file remains a valid snapshot of the last known good state, but it does not automatically "reject" the edit to the .csproj or Directory.Packages.props. To actually commit the version change to the lock file, you must explicitly use:
dotnet restore --force
Without this flag, the lock file is never updated during a failed resolution, which confirms that the only way to return to a fully consistent state after a failed upgrade is to manually revert the project or central props file via source control.