This unblocks main and every open pull request. They all went red together on a restore that never reached a compiler:
error NU1004: The package reference Microsoft.NET.ILLink.Tasks version has changed
from [10.0.10, ) to [10.0.11, ). The packages lock file is inconsistent with the
project dependencies so restore can't be run in locked mode.
Nothing in any of those commits touched a package. .NET had shipped SDK 10.0.400.
The version in the lock files was never this repository's to decide
Microsoft.NET.ILLink.Tasks is referenced by nothing here. The SDK adds it to any project setting IsTrimmable or IsAotCompatible — DodoSSH.Contracts and DodoSSH.Crypto do, and the Android head gets it from trimming being on by default — and supplies the version itself from the KnownILLinkPack item in its own Microsoft.NETCoreSdk.BundledVersions.props. SDK 10.0.302 says 10.0.10; 10.0.400 says 10.0.11.
packages.lock.json records that as a Direct reference with a requested range, so the committed file actually means whichever SDK last ran a restore. global.json says rollForward: latestMinor, so setup-dotnet installs the newest 10.x SDK that exists on the morning it runs. The gate did its job — an unreviewed dependency change is what it is there to stop — but the change it caught was not one anybody could have reviewed, and it recurs on every servicing release.
Regenerating the lock files alone would have been the worse repair, and not only because it holds until the next release: it cannot be done from a machine whose newest SDK is older than the runner's. Every SDK on mine tops out at 10.0.302, which writes 10.0.10 straight back and re-breaks CI. The recorded version would flip according to who restored last — the state locking exists to prevent.
The change
The version is pinned in a new Directory.Build.targets, and the three lock files are regenerated against the pin. It is an Update on the SDK's item rather than a PackageVersion because the reference is implicit, so central package management is never consulted; it sits inside a target because conditioning on %(TargetFramework) is item batching, which is legal in a target and MSB4191 during evaluation.
Pinned forward to 10.0.11 rather than back to 10.0.10, which would have been a one-liner with no lock churn. Holding the trimmer a release behind the framework it analyses is how a missed trim warning happens, and taking the newer one makes the bump a reviewed diff — which is what the gate was asking for.
Verification, against the SDK that broke it
check
SDK
result
dotnet restore DodoSSH.slnx --locked-mode in sdk:10.0-alpine — ci.yml's line
10.0.400
exit 0
locked-mode restore of DodoSSH.Client.Android with the android workload on sdk:10.0-noble — ci-android.sh's line
10.0.400
exit 0
the same solution restore locally
10.0.302
exit 0
One set of lock files satisfying both SDKs is the point of the pin, and the third row is what demonstrates it.
Release build clean: 0 errors, and 0 IL-prefixed diagnostics from the newer analyser on the two trimmable projects. 1,869 tests over 19 suites, none failing.
One caution recorded in the commit: --force-evaluate on Windows rewrites every lock file with CRLF, and 23 of the 26 had no content change at all. Only the three that really moved are here.
**This unblocks `main` and every open pull request.** They all went red together on a restore that never reached a compiler:
```
error NU1004: The package reference Microsoft.NET.ILLink.Tasks version has changed
from [10.0.10, ) to [10.0.11, ). The packages lock file is inconsistent with the
project dependencies so restore can't be run in locked mode.
```
Nothing in any of those commits touched a package. .NET had shipped SDK 10.0.400.
### The version in the lock files was never this repository's to decide
`Microsoft.NET.ILLink.Tasks` is referenced by nothing here. The SDK adds it to any project setting `IsTrimmable` or `IsAotCompatible` — `DodoSSH.Contracts` and `DodoSSH.Crypto` do, and the Android head gets it from trimming being on by default — and supplies the version itself from the `KnownILLinkPack` item in its own `Microsoft.NETCoreSdk.BundledVersions.props`. SDK 10.0.302 says 10.0.10; 10.0.400 says 10.0.11.
`packages.lock.json` records that as a Direct reference with a requested range, so the committed file actually means *whichever SDK last ran a restore*. `global.json` says `rollForward: latestMinor`, so `setup-dotnet` installs the newest 10.x SDK that exists on the morning it runs. The gate did its job — an unreviewed dependency change is what it is there to stop — but the change it caught was not one anybody could have reviewed, and it recurs on every servicing release.
Regenerating the lock files alone would have been the worse repair, and not only because it holds until the next release: it cannot be done from a machine whose newest SDK is older than the runner's. Every SDK on mine tops out at 10.0.302, which writes 10.0.10 straight back and re-breaks CI. The recorded version would flip according to who restored last — the state locking exists to prevent.
### The change
The version is pinned in a new `Directory.Build.targets`, and the three lock files are regenerated against the pin. It is an `Update` on the SDK's item rather than a `PackageVersion` because the reference is implicit, so central package management is never consulted; it sits inside a target because conditioning on `%(TargetFramework)` is item batching, which is legal in a target and `MSB4191` during evaluation.
Pinned **forward** to 10.0.11 rather than back to 10.0.10, which would have been a one-liner with no lock churn. Holding the trimmer a release behind the framework it analyses is how a missed trim warning happens, and taking the newer one makes the bump a reviewed diff — which is what the gate was asking for.
### Verification, against the SDK that broke it
| check | SDK | result |
| --- | --- | --- |
| `dotnet restore DodoSSH.slnx --locked-mode` in `sdk:10.0-alpine` — ci.yml's line | 10.0.400 | exit 0 |
| locked-mode restore of `DodoSSH.Client.Android` with the android workload on `sdk:10.0-noble` — ci-android.sh's line | 10.0.400 | exit 0 |
| the same solution restore locally | 10.0.302 | exit 0 |
One set of lock files satisfying both SDKs is the point of the pin, and the third row is what demonstrates it.
Release build clean: 0 errors, and 0 IL-prefixed diagnostics from the newer analyser on the two trimmable projects. 1,869 tests over 19 suites, none failing.
One caution recorded in the commit: `--force-evaluate` on Windows rewrites every lock file with CRLF, and 23 of the 26 had no content change at all. Only the three that really moved are here.
CI went red across the whole repository — main's run 125 and every open pull
request at once — on a restore that never reached a compiler:
error NU1004: The package reference Microsoft.NET.ILLink.Tasks version has
changed from [10.0.10, ) to [10.0.11, ). The packages lock file is
inconsistent with the project dependencies so restore can't be run in
locked mode.
Nothing in any of those commits touched a package. .NET had shipped SDK 10.0.400.
◆ THE VERSION IN THE LOCK FILES WAS NEVER THIS REPOSITORY'S TO DECIDE.
Microsoft.NET.ILLink.Tasks is referenced by nothing here. The SDK adds it to any
project setting IsTrimmable or IsAotCompatible — DodoSSH.Contracts and
DodoSSH.Crypto do, and the Android head gets it from trimming being on by
default — and it supplies the version itself, from the KnownILLinkPack item in
its own Microsoft.NETCoreSdk.BundledVersions.props. 10.0.302 says 10.0.10;
10.0.400 says 10.0.11.
packages.lock.json records that as a Direct reference with a requested range, so
what the committed file actually means is "whichever SDK last ran a restore".
global.json says rollForward: latestMinor, so setup-dotnet installs the newest
10.x SDK that exists on the morning it runs. The gate did its job — an unreviewed
dependency change is exactly what it is there to stop — but the change it caught
was not one anybody could have reviewed, and it will recur on every servicing
release.
Regenerating the lock files alone would have been the worse repair, and not only
because it holds until the next release. It cannot be done from this machine at
all: every SDK installed here tops out at 10.0.302, which writes 10.0.10 straight
back and re-breaks CI. The recorded version would flip according to who restored
last — the precise state locking exists to prevent.
So the version is pinned in Directory.Build.targets and the three lock files are
regenerated against the pin. It is an Update on the SDK's item rather than a
PackageVersion in Directory.Packages.props because the reference is implicit:
the SDK supplies a version, so central package management is never consulted. It
sits in a target because the conditioning is on %(TargetFramework) — all the
KnownILLinkPack items share one identity and only that metadata separates
net10.0's from net8.0's — and item batching in a condition is legal inside a
target and MSB4191 during evaluation.
Pinned forward to 10.0.11 rather than back to 10.0.10, which would have been a
one-line change with no lock file churn. Holding the trimmer a release behind the
framework it analyses to dodge an error is how a missed trim warning happens, and
taking the newer one makes the bump a reviewed diff, which is what the gate was
asking for.
Verified against the SDK that broke it rather than only the one here:
- sdk:10.0-alpine, 10.0.400, `dotnet restore DodoSSH.slnx --locked-mode` —
exit 0. That is ci.yml's line, on CI's SDK.
- the android workload on sdk:10.0-noble, 10.0.400, locked-mode restore of
DodoSSH.Client.Android — exit 0. That is scripts/ci-android.sh's line.
- locally on 10.0.302, the same locked-mode restore of the solution — exit 0.
One set of lock files satisfying both SDKs is the whole point of the pin, and the
third check is the one that demonstrates it.
Release build clean: 0 errors, and 0 IL-prefixed diagnostics from the newer
analyser on the two trimmable projects. 1,869 tests over 19 suites, none failing.
A caution for the next person, learned the hard way here: `--force-evaluate` on
Windows rewrites every lock file it touches with CRLF, and 23 of the 26 had no
content change at all. Only the three that really moved are in this commit.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
This unblocks
mainand every open pull request. They all went red together on a restore that never reached a compiler:Nothing in any of those commits touched a package. .NET had shipped SDK 10.0.400.
The version in the lock files was never this repository's to decide
Microsoft.NET.ILLink.Tasksis referenced by nothing here. The SDK adds it to any project settingIsTrimmableorIsAotCompatible—DodoSSH.ContractsandDodoSSH.Cryptodo, and the Android head gets it from trimming being on by default — and supplies the version itself from theKnownILLinkPackitem in its ownMicrosoft.NETCoreSdk.BundledVersions.props. SDK 10.0.302 says 10.0.10; 10.0.400 says 10.0.11.packages.lock.jsonrecords that as a Direct reference with a requested range, so the committed file actually means whichever SDK last ran a restore.global.jsonsaysrollForward: latestMinor, sosetup-dotnetinstalls the newest 10.x SDK that exists on the morning it runs. The gate did its job — an unreviewed dependency change is what it is there to stop — but the change it caught was not one anybody could have reviewed, and it recurs on every servicing release.Regenerating the lock files alone would have been the worse repair, and not only because it holds until the next release: it cannot be done from a machine whose newest SDK is older than the runner's. Every SDK on mine tops out at 10.0.302, which writes 10.0.10 straight back and re-breaks CI. The recorded version would flip according to who restored last — the state locking exists to prevent.
The change
The version is pinned in a new
Directory.Build.targets, and the three lock files are regenerated against the pin. It is anUpdateon the SDK's item rather than aPackageVersionbecause the reference is implicit, so central package management is never consulted; it sits inside a target because conditioning on%(TargetFramework)is item batching, which is legal in a target andMSB4191during evaluation.Pinned forward to 10.0.11 rather than back to 10.0.10, which would have been a one-liner with no lock churn. Holding the trimmer a release behind the framework it analyses is how a missed trim warning happens, and taking the newer one makes the bump a reviewed diff — which is what the gate was asking for.
Verification, against the SDK that broke it
dotnet restore DodoSSH.slnx --locked-modeinsdk:10.0-alpine— ci.yml's lineDodoSSH.Client.Androidwith the android workload onsdk:10.0-noble— ci-android.sh's lineOne set of lock files satisfying both SDKs is the point of the pin, and the third row is what demonstrates it.
Release build clean: 0 errors, and 0 IL-prefixed diagnostics from the newer analyser on the two trimmable projects. 1,869 tests over 19 suites, none failing.
One caution recorded in the commit:
--force-evaluateon Windows rewrites every lock file with CRLF, and 23 of the 26 had no content change at all. Only the three that really moved are here.CI went red across the whole repository — main's run 125 and every open pull request at once — on a restore that never reached a compiler: error NU1004: The package reference Microsoft.NET.ILLink.Tasks version has changed from [10.0.10, ) to [10.0.11, ). The packages lock file is inconsistent with the project dependencies so restore can't be run in locked mode. Nothing in any of those commits touched a package. .NET had shipped SDK 10.0.400. ◆ THE VERSION IN THE LOCK FILES WAS NEVER THIS REPOSITORY'S TO DECIDE. Microsoft.NET.ILLink.Tasks is referenced by nothing here. The SDK adds it to any project setting IsTrimmable or IsAotCompatible — DodoSSH.Contracts and DodoSSH.Crypto do, and the Android head gets it from trimming being on by default — and it supplies the version itself, from the KnownILLinkPack item in its own Microsoft.NETCoreSdk.BundledVersions.props. 10.0.302 says 10.0.10; 10.0.400 says 10.0.11. packages.lock.json records that as a Direct reference with a requested range, so what the committed file actually means is "whichever SDK last ran a restore". global.json says rollForward: latestMinor, so setup-dotnet installs the newest 10.x SDK that exists on the morning it runs. The gate did its job — an unreviewed dependency change is exactly what it is there to stop — but the change it caught was not one anybody could have reviewed, and it will recur on every servicing release. Regenerating the lock files alone would have been the worse repair, and not only because it holds until the next release. It cannot be done from this machine at all: every SDK installed here tops out at 10.0.302, which writes 10.0.10 straight back and re-breaks CI. The recorded version would flip according to who restored last — the precise state locking exists to prevent. So the version is pinned in Directory.Build.targets and the three lock files are regenerated against the pin. It is an Update on the SDK's item rather than a PackageVersion in Directory.Packages.props because the reference is implicit: the SDK supplies a version, so central package management is never consulted. It sits in a target because the conditioning is on %(TargetFramework) — all the KnownILLinkPack items share one identity and only that metadata separates net10.0's from net8.0's — and item batching in a condition is legal inside a target and MSB4191 during evaluation. Pinned forward to 10.0.11 rather than back to 10.0.10, which would have been a one-line change with no lock file churn. Holding the trimmer a release behind the framework it analyses to dodge an error is how a missed trim warning happens, and taking the newer one makes the bump a reviewed diff, which is what the gate was asking for. Verified against the SDK that broke it rather than only the one here: - sdk:10.0-alpine, 10.0.400, `dotnet restore DodoSSH.slnx --locked-mode` — exit 0. That is ci.yml's line, on CI's SDK. - the android workload on sdk:10.0-noble, 10.0.400, locked-mode restore of DodoSSH.Client.Android — exit 0. That is scripts/ci-android.sh's line. - locally on 10.0.302, the same locked-mode restore of the solution — exit 0. One set of lock files satisfying both SDKs is the whole point of the pin, and the third check is the one that demonstrates it. Release build clean: 0 errors, and 0 IL-prefixed diagnostics from the newer analyser on the two trimmable projects. 1,869 tests over 19 suites, none failing. A caution for the next person, learned the hard way here: `--force-evaluate` on Windows rewrites every lock file it touches with CRLF, and 23 of the 26 had no content change at all. Only the three that really moved are in this commit.