Lock Files and Locked Mode
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
A restore is a function of the project’s references and of what the feeds offer at that moment: a newly published version, a deleted one or a re-uploaded package can change the graph without any commit. A lock file records the whole resolved graph — every direct and transitive package, its version and a hash of its content — and locked mode makes restore fail instead of silently differing. Ledgerline enables both. This page shows them working and failing with NuGet 7.9.0 (.NET SDK 10.0.402), on the scratch project and synthetic packages of Dependency Resolution.
Enabling the lock file
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
Ledgerline sets it in Directory.Build.props, so every project writes a packages.lock.json next to its project
file. Once a lock file exists, restore uses it even if the property is removed. For a project referencing
Ledgerline.Rates 1.0.0:
{
"version": 1,
"dependencies": {
"net10.0": {
"Ledgerline.Rates": {
"type": "Direct",
"requested": "[1.0.0, )",
"resolved": "1.0.0",
"contentHash": "BWREbaJ1wXNuacUeiRinzc7hFx3DfPIPPtfV0QQGjnibJJEy/KOnGzviwYcW9CkgLwkL5yow86pIkMMD9H2Beg==",
"dependencies": {
"Ledgerline.Money": "1.2.0"
}
},
"Ledgerline.Money": {
"type": "Transitive",
"resolved": "1.5.0",
"contentHash": "ikqFv5yDeM68b02nB3Ou0dYlkIRu7EjVvuyM5d8glrcPRVDYHvG7wAqqbO3sMMaMYnvX2vCdcEF6+9KYKxy0fg=="
}
}
}
}
-
One section per target framework (and per RID with
RuntimeIdentifiers). -
requestedis the range from the project,resolvedthe version chosen,dependenciesthe package’s own declared minimums (Ledgerline.Money1.2.0, resolved to 1.5.0 as on the dependency-resolution page). -
contentHashis the SHA-512 recorded in the package’s.nupkg.metadata(How Package Installation Works). -
The format is versioned: this project wrote
"version": 1; Ledgerline's projects, which use Central Package Management, write"version": 2.
Locked mode
Without locked mode, a restore that sees a changed dependency simply re-resolves and rewrites the lock file. In CI
that is never what you want. dotnet restore --locked-mode (or RestoreLockedMode=true) restores exactly the
recorded graph or fails.
A changed reference — NU1004. The project switched from Ledgerline.Rates to Ledgerline.Fx without updating
the lock file:
~/ledgerline/samples/Deps/Deps.csproj : error NU1004: A new package reference was found Ledgerline.Fx for the project target framework net10.0.The packages lock file is inconsistent with the project dependencies so restore can't be run in locked mode. Disable the RestoreLockedMode MSBuild property or pass an explicit --force-evaluate option to run restore to update the lock file.
Changed package content — NU1403. Ledgerline.Money 1.5.0 was replaced on the feed with a different package of
the same ID and version (possible on any feed that allows overwriting — nuget.org does not), and removed from the
global-packages folder:
~/ledgerline/samples/Deps/Deps.csproj : error NU1403: Package content hash validation failed for Ledgerline.Money.1.5.0. The package is different than the last restore.
This is the lock file’s supply-chain value: same ID, same version, different bytes — caught (Supply-Chain Security).
Source: Restore behavior with lock file, NU1004, NU1403.
Updating the lock file
dotnet restore --force-evaluate (RestoreForceEvaluate) re-resolves and rewrites the lock file. Run against the
tampered feed above, it succeeded silently and replaced the recorded hash of Ledgerline.Money 1.5.0 with the hash of
the tampered package:
before: "contentHash": "ikqFv5yDeM68b02nB3Ou0dYlkIRu7EjVvuyM5d8glrcPRVDYHvG7wAqqbO3sMMaMYnvX2vCdcEF6+9KYKxy0fg=="
after: "contentHash": "LdP9SOr5OSn4BbyjwGXHzSss1YrRL64cUfXKylfja2drIQz5JrsjVVuoX0TCtAtz/HMcULkU0WDOG0S3V2VXOg=="
A lock-file diff that changes a hash without changing the version is therefore something to investigate, not to merge. Review lock-file changes like code.
Passing --locked-mode and --force-evaluate together is contradictory; force-evaluate wins. SDK 10.0.402 said
nothing; with -p:SdkAnalysisLevel=11.0.100 — the default in the .NET 11 SDK — it warns:
~/.dotnet/sdk/10.0.402/NuGet.targets(198,5): warning NU1512: RestoreForceEvaluate overrides RestoreLockedMode; the packages lock file will be re-evaluated and regenerated, and locked mode is ignored.
Source: NuGet restore properties, NU1512.
A missing lock file is not an error
NuGetLockFilePath moves the file (-p:NuGetLockFilePath=lock/custom.lock.json). A locked-mode restore pointed at a
path where no lock file existed did not fail — it created lock/custom.lock.json. Locked mode compares against
a lock file; it does not require one. A CI job should also check that the expected packages.lock.json files are
committed (for example, git diff --exit-code after the restore).
Source: Lock file extensibility.
Lock files with other features
-
Central Package Management — the lock file records the central versions; transitive pinning adds
CentralTransitiveentries (format version 2). -
Pruning — packages pruned for
net10.0are absent from the lock file; enabling pruning on an existing project shrinks the next regenerated lock file, and the locked-mode check is pruning-aware (PackageReference). -
SDK version — implicit package references and pruning data come from the SDK, so a different SDK can make a locked restore fail; the documentation suggests
global.jsonwithrollForward: disablefor repositories that lock (Installing and Selecting SDKs). -
Floating versions — locked at the version they floated to until
--force-evaluate.
What to commit
Commit lock files for applications, services, tools and test projects — anything at the top of a dependency
chain. For a library, a committed lock file only describes the library’s own test restore; consumers resolve
their own graph. Ledgerline commits all of them and runs dotnet restore --locked-mode first in CI
(CI Builds and Caching); the lock files also
serve as the cache key for the NuGet folder.
Common mistakes
-
Enabling the lock file but not locked mode in CI — the file is rewritten instead of enforced.
-
Merging a lock file whose hashes changed without a version change —
--force-evaluateaccepts tampered content. -
Relying on locked mode to require a lock file — it creates a missing one.
-
Upgrading the SDK and being surprised by NU1004 after pruning changes — regenerate deliberately.