dotnet / dotnet/sdk

dotnetup does not preserve global.json SDK roll-forward minimum `latestX` semantics

Open
#56,227 1 comment 0 reactions 1 assignee Claimed by @dsplaisted View on GitHub
Area-dotnetup Bug needs team triage
Dominant language
C#
Stars
3.2k
Forks
1.3k
PR merge metrics
PR metrics pending

Description

### Description

Dotnetup converts a `global.json` SDK requirement into a dotnetup channel string. The conversion loses information required to implement the SDK host's roll-forward semantics correctly.

Current mappings include:

| `sdk.rollForward` | Tracked dotnetup channel |
| --- | --- |
| `latestPatch` or omitted | SDK feature band, such as `11.0.1xx` |
| `latestFeature` | Major/minor, such as `11.0` |
| `latestMinor` | Major, such as `11` |
| `latestMajor` | `latest` |
| `disable`, `patch`, `feature`, `minor`, `major` | Fully specified version |

### Problems

#### `latestX` policies lose the requested-version lower bound

For example:

```json
{
"sdk": {
"version": "11.0.100-preview.6.26489.188",
"rollForward": "latestPatch",
"allowPrerelease": true
}
}
```

is represented as `11.0.1xx`. `UpdateChannel.Matches` checks only the major, minor, and SDK feature band. Consequently, an older preview 5 SDK is considered a match even though the original requirement has preview 6 as its minimum.

The same problem applies at broader levels:

- `latestFeature` loses the minimum feature band.
- `latestMinor` loses the minimum minor version.
- `latestMajor` loses the minimum major version.

This may be masked during normal online installation because resolving the latest published version often returns a newer version. It remains observable during offline garbage collection, with mixed installed SDKs, or when the requested SDK is newer than the public release metadata.

#### Non-`latest` policies become overly restrictive

`patch`, `feature`, `minor`, and `major` are converted to an exact version. This avoids rolling backward, but it also discards the fallback roll-forward behavior those policies permit when the requested SDK is unavailable.

#### Prerelease policy is lost

`sdk.allowPrerelease` is parsed but is not passed into channel derivation or resolution. This is also tracked in #53396.

### User impact

- Garbage collection may retain an older installed SDK that does not satisfy the original `global.json`.
- Installation may resolve a different SDK from the SDK host, particularly for future/private versions or mixed stable and prerelease environments.
- Removing another SDK may expose a supposedly retained GlobalJson requirement that `dotnet` cannot actually resolve.

### Proposed behavior

Do not represent a GlobalJson requirement solely as a dotnetup channel. Retain and evaluate at least:

- requested SDK version
- `rollForward`
- `allowPrerelease`

Resolution and garbage-collection matching should use semantics equivalent to the SDK host, including the requested version as the lower bound where applicable.

### Acceptance criteria

- No `latestX` policy selects an SDK older than the version specified by `global.json`.
- `patch`, `feature`, `minor`, and `major` perform their permitted fallback behavior when the exact SDK is unavailable.
- Stable/prerelease eligibility respects `allowPrerelease`.
- Online installation and offline GC reach decisions consistent with the SDK host for the same installed-version set.

Related: #53396, #54874, and #54916.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.