Without a global.json, the dotnet CLI silently uses the latest SDK installed on the machine. That means a teammate who just installed a newer SDK (or a CI image that updated overnight) can build your repo with a different compiler, different analyzers, and different language version than everyone else - and you find out via a broken build or, worse, subtly different output.
A one-file fix makes the SDK version part of the repository.
A global.json file at the root of your repository tells the dotnet CLI which SDK version to use:
{"sdk": {"version": "10.0.100","rollForward": "latestFeature","allowPrerelease": false}}
✅ Good example - SDK pinned to the 10.0.x band, newer feature bands allowed, previews blocked
You can generate one with:
dotnet new globaljson --sdk-version 10.0.100
Pinning to an exact version is usually too strict - every developer would need that precise patch installed. The rollForward setting controls how flexible the match is:
latestPatch - same feature band, latest patch (e.g. 10.0.100 → 10.0.1xx). Safest for strict reproducibilitylatestFeature - same major.minor, latest feature band and patch (e.g. 10.0.100 → 10.0.2xx). Good default for most teamslatestMajor - effectively "use the newest SDK", which is the same as having no global.json - avoiddisable - exact version only. Reserve for build environments you fully controlMake your pipeline read the same file instead of duplicating the version:
- uses: actions/setup-dotnet@v4with:dotnet-version: 10.0.x
❌ Bad example - CI hardcodes an SDK that will drift from what developers use
- uses: actions/setup-dotnet@v4with:global-json-file: global.json
✅ Good example - CI resolves the SDK from global.json, so there is a single source of truth
global.json at the repository root so it governs every project - the CLI searches upward from the working directory and uses the first one it finds