Releasing
September 16, 2026 · View on GitHub
Each product has its own version in eng/Versions.props and its own
CHANGELOG.md. The Prepare release workflow bumps
versions and opens a pull request. After that PR is merged, Publish
builds and publishes the packages.
Run the commands below from the repository root.
- Prepare a release
- Write the overview for a new stable major
- Publish
- Recover a failed publish
- Hotfix an earlier major
- Update dependency floors
- Check a prepared but unpublished version
Prepare a release
Open Actions → Prepare release on the branch you want to release. Each product has a field
accepting prerelease, release, patch, minor, major, or an explicit version such as
2.1.0-rc.1. Leave a field empty to skip that product.
One run bumps the selected products, promotes each one's ## Unreleased changelog section,
and opens one PR for the batch. Review the versions and release notes before merging.
Prepare release publishes nothing.
The version-update script can also be run locally. For example, to promote Delivery's current prerelease to stable:
dotnet run eng/scripts/update-version.cs -- delivery release
Use the workflow for the release PR; feature PRs must not change eng/Versions.props.
Releasing a new stable major
A release page contains only that version's changelog section. When promoting a release candidate, the section usually describes changes since the previous candidate. Add an overview for readers upgrading from the previous stable major.
In the release PR, put the overview between the version heading and the first ###, without
a heading of its own. It should cover, in order:
- The new release line and target framework.
- What users of the previous stable major need to change, separating API changes from behavior changes that compile unchanged.
- A link to the upgrade guide, including earlier guides if users can skip a major.
- Where to find the detail: the sections below cover changes since the last prerelease; earlier prerelease entries retain the rest of the history.
Keep links absolute so they work on the GitHub Release page. Do not copy all the prerelease entries into the overview; the upgrade guide already brings the migration steps together.
Render the notes from the release branch to check them. For example, for Delivery 20.0.0:
dotnet run eng/scripts/release-notes.cs -- delivery-v20.0.0
Publish rejects a new stable major without an overview, and rejects an empty release entry. Both checks run during the dry run, before anything is pushed.
Publish
After merging the release PR, open Actions → Publish on the same branch. It publishes products whose declared version is missing from NuGet. Any product can be released independently.
Publish defaults to a dry run: it builds, packs, and renders the release notes without pushing. Run it once to inspect the output, then run it again with dry run disabled. The packages and notes are available as workflow artifacts in both cases.
The workflow refuses to publish a product if its changelog has no entry for the declared version
or a cross-product dependency floor names a version that is not on NuGet. It records each release
with a <product>-v<version> tag and a GitHub Release whose notes come from the changelog.
Repository links in those notes are pinned to the tag; links inside the destination guides may
still point to main.
The job uses the nuget.org GitHub environment. Its settings determine which branches may publish
and whether approval is required. From a branch other than main or vnext, name the products
to publish explicitly.
When a publish run fails
NuGet uploads are immutable, and a product may contain several packages. Publish creates the version's tag before pushing any package, binding that version to one commit. The GitHub Release stays a draft until every package and asset has been uploaded.
To finish an interrupted publish, re-run that run in Actions. It uses the original commit, skips completed uploads, and finishes the remaining work. A fresh dispatch from a later commit is refused for that version. Only if none of the version's packages reached NuGet may its tag be deleted and publishing dispatched again.
A fresh dispatch also finds products whose packages are on NuGet but whose GitHub Release is missing or still a draft. Completed products are skipped, with a notice if explicitly selected.
Hotfixing an earlier major
Branch from the last tag of that line as maintenance/<name> and cherry-pick the fix. Run both
workflows from that branch: Prepare release opens its PR against the branch, and Publish needs
the product named explicitly. An older branch declares every product at its version from that time.
Cross-product dependency floors
Releasing a product does not update the version its siblings depend on. Those minimum versions
live in Directory.Packages.props. Raise a floor in a separate PR,
after the dependency has been published, and only when the consuming code needs the newer API.
See Changing an API that another product consumes
for the sequence.
A dependent product's first stable release needs the floor raised in between: publish the products
it depends on, raise the floors, then publish the dependent. Otherwise its GA package declares a
prerelease dependency, and a consumer installing it resolves that prerelease. Publish takes a product
list (delivery management sync, then aspnetcore model-generator) so the two waves stay explicit.
Packages inside one product are unaffected - they reference each other by project and ship together.
To check the current floors:
dotnet run eng/scripts/dependency-floors.cs
Prepare release includes the same report in its PR, and Actions → Dependency floors runs it monthly. A floor behind the latest release is reported, not rejected. A floor that names a version absent from NuGet fails the check.
Prepared but not published
A merged release PR may declare a version that has not yet been published. Check the status with:
dotnet run eng/scripts/release-status.cs
Do not prepare that product again before publishing or abandoning the prepared version: another
bump would skip the unpublished version. To abandon it, restore the previous version in
eng/Versions.props and remove the prepared ## <version> (<date>) heading so its notes return
to ## Unreleased.