CI starts failing after a runner image update, but the code hasn’t changed. Check the SDK. Without an applicable global.json, the .NET CLI selects the highest installed SDK, so a machine update can change your build tools. The project’s TargetFramework does not pin them.

Commit a global.json at the repository root and choose how much movement you allow:

{
  "sdk": {
    "version": "10.0.100",
    "rollForward": "latestPatch",
    "allowPrerelease": false
  }
}

The version is illustrative. Replace it with the full SDK version your team has tested.

To scaffold the file, run this from the repository root:

dotnet new globaljson --sdk-version 10.0.100 --roll-forward latestPatch

Then add "allowPrerelease": false inside sdk to match the example above.

Choose the update boundary

With this configuration, latestPatch selects the highest installed 10.0.1xx SDK at or above 10.0.100. It excludes 10.0.200 and preview SDKs. If no eligible SDK exists, selection fails.

Decide which updates the repository can accept:

PolicyBehavior for 10.0.100
disableRequires exactly 10.0.100.
latestPatchAllows newer installed patches in the 10.0.1xx feature band.
latestFeatureAllows newer installed feature bands and patches within 10.0.

Omitting rollForward defaults to patch: it prefers the exact version when installed, then falls back to a newer patch in that band. latestPatch chooses the highest eligible patch even when the requested version is present. See Microsoft’s policy reference.

Two machines can still use different patches under latestPatch. Microsoft recommends disable for package lock files. An exact pin also means your team must review and update that version.

Check what CI actually uses

global.json selects among available SDKs. The build environment must provide one that satisfies the repository policy. Configure CI’s SDK setup step to match that policy, then run these commands from the repository root before building:

dotnet --list-sdks
dotnet --version

The first lists installed SDKs. The second prints the selected SDK. Keep that output in the build log so a tooling difference is visible when a build fails.

For CLI SDK selection, dotnet searches upward from the current working directory for global.json. Run it inside the checkout: passing a project path alone does not change where that search starts. MSBuild’s project SDK resolver has a separate lookup that can start from the solution or project directory. Microsoft’s global.json reference explains both paths.

Use latestPatch if patch differences are acceptable for the repository. If you’re investigating a build regression and need everyone on the same SDK, use disable. Keep SDK updates in the maintenance queue. A version left in global.json can be forgotten as easily as any other dependency.