- name
- release
- description
- Orchestrate an MSBuild release: create the tracking issue, branch, configure DARC channels and subscriptions, bump version in main, final-brand the release branch, insert into VS, and publish post-GA. Covers the full monthly release lifecycle aligned with VS shipping cadence.
- argument-hint
- Specify which phase to execute (0-5) and provide required inputs (THIS_RELEASE_VERSION, NEXT_VERSION, etc.)
# MSBuild Release Orchestration
This skill guides an agent through the MSBuild release process defined in [`documentation/release-checklist.md`](../../../documentation/release-checklist.md). The checklist is the single source of truth — this skill provides context on how to execute it.
## Overview
MSBuild is a **component** that gets inserted into Visual Studio. VS ships monthly; MSBuild must branch and prepare its bits **before** VS is ready to take them. See the [release process doc](../../../documentation/release.md#how-msbuild-releases-flow-into-vs) for the full timeline diagram.
The insertion pipeline routes MSBuild branches to VS branches:
- `main` → VS `main` (daily canary)
- `vs*` release branch → VS `main` (replaces `main` → `main` after branch snap)
VS handles the progression from `main` → `rel/insiders` → `rel/stable` on its own schedule. MSBuild's responsibility is to have final-branded bits in VS `main` before the insiders snap date.
Each monthly VS release produces:
- A new `vs*` branch from `main`
- Final branding on that branch
- A version bump in `main`
- DARC channel/subscription updates
- A VS insertion
- Post-GA publishing to nuget.org and docs
The process is organized into **6 timeline-gated phases** (0–5), each with an explicit trigger.
> **Execution model**: This skill is designed for an **interactive Copilot session**. The agent walks through each phase step-by-step, but every command that modifies state (git push, DARC writes, pipeline changes, PR creation) requires **user approval** before execution. Read-only queries (DARC get-*, git log, etc.) can run without approval.
## Required Inputs
Before starting any phase, ensure you have these values (the user must provide them — version increments are irregular and cannot be computed):
| Input | Example | How to determine |
|---|---|---|
| `PREVIOUS_RELEASE_VERSION` | `18.9` | Previous release branch version: the highest `vs*` version lower than `THIS_RELEASE_VERSION` (list candidates with `git branch -r --list 'origin/vs*'`). |
| `PREVIOUS_RELEASE_EXACT_VERSION` | `18.9.6` | The version the previous release actually shipped as. From the previous release's tracking issue, or `git tag --list 'v18.9.*'`. Used by Phase 5.3a. |
| `THIS_RELEASE_VERSION` | `18.10` | Current `VersionPrefix` in `eng/Versions.props` (drop `.0`) |
| `NEXT_VERSION` | `18.11` | User-provided — not computable from current version |
| `BRANCH_SNAP_DATE` | `YYYY-MM-DD` | From [VS-Dates wiki](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/49807/VS-Dates) — when MSBuild branches `vs*` from main, insertion targets VS `main` |
| `INSIDERS_SNAP_DATE` | `YYYY-MM-DD` | From [VS-Dates wiki](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/49807/VS-Dates) — when VS snaps `main` → `rel/insiders`; final-branded bits must be in VS `main` before this |
| `STABLE_SNAP_DATE` | `YYYY-MM-DD` | From [VS-Dates wiki](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/49807/VS-Dates) — when VS promotes `rel/insiders` → `rel/stable` |
| `VS_SHIP_DATE` | `YYYY-MM-DD` | When VS ships publicly (GA) — triggers post-release tasks |
| `PACKAGE_VALIDATION_BASELINE_VERSION` | `18.10.0-1.26378.2` | See [How to determine `PACKAGE_VALIDATION_BASELINE_VERSION`](#how-to-determine-package_validation_baseline_version) below — non-trivial: most "obvious" picks are wrong. |
> Version examples above track the current cycle (`eng/Versions.props` `VersionPrefix` is `18.10.0`). Dates are intentionally shown as a format only — always read the real ones from the VS-Dates wiki.
### How to determine `PACKAGE_VALIDATION_BASELINE_VERSION`
**The value is the latest `{{THIS_RELEASE_VERSION}}.0-<label>.<shortDate>.<rev>` MSBuild package that is both:**
_`<label>` is `PreReleaseVersionLabel` from `eng/Versions.props` (currently `1`), so the versions look like `18.11.0-1.26426.2`. Do **not** assume the legacy `-preview-NNNNN-NN` shape — MSBuild no longer produces it._
1. **Published on the public [dotnet-tools feed](https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-tools)** — this is the feed the official build publishes to and that ApiCompat restores baselines from. If the version isn't here, ApiCompat fails with `NU1102`.
2. **Produced from a commit reachable from `vs{{THIS_RELEASE_VERSION}}`** — i.e. a commit on `vs{{THIS_RELEASE_VERSION}}`, or the `main` commit `vs{{THIS_RELEASE_VERSION}}` was branched from.
**Two tempting wrong answers — and why they're wrong:**
| Wrong pick | Why it fails |
|---|---|
| ❌ The release-versioned `{{THIS_RELEASE_VERSION}}.X` package that ships in VS / on nuget.org | Since [#14277](https://github.com/dotnet/msbuild/pull/14277) release branches build and insert **prerelease** versions, exactly like `main`; the release-versioned variants are produced by NuGetRepack at manual publish time. They never exist on the public CI feed, so ApiCompat cannot restore them. |
| ❌ Blindly the most recent `{{THIS_RELEASE_VERSION}}.0-*` on `dotnet-tools` | After `vs{{THIS_RELEASE_VERSION}}` branches, `main` keeps producing `{{THIS_RELEASE_VERSION}}.0-*` until **this** main-bump PR merges — so the most recent feed entries may be `{{NEXT_VERSION}}`-content builds wearing `{{THIS_RELEASE_VERSION}}` branding. Picking one drifts the API baseline forward and silently hides real compat breaks. |
**Timing caveat:** the branch-point build publishes to `dotnet-tools` asynchronously. If you branch and bump on the same day, the script may report the correct candidate as `[NOT on feed]` — wait for publication and re-run rather than substituting an older build.
**Procedure:** run the helper — it does the whole resolution mechanically (requires `az login` with devdiv access):
```
pwsh ./scripts/Get-PackageValidationBaseline.ps1 -ThisReleaseVersion {{THIS_RELEASE_VERSION}}
# -> prints e.g. 18.11.0-1.26426.2
```
It computes `git merge-base origin/main origin/vs{{THIS_RELEASE_VERSION}}`, finds the matching successful build in [pipeline 9434](https://devdiv.visualstudio.com/DevDiv/_build?definitionId=9434), derives the package version from the OfficialBuildId, and verifies it on the dotnet-tools feed. If it fails, read the script's own `.DESCRIPTION` header for the manual equivalent rather than reproducing it here.
### Prerequisites
- gh cli
- az cli
- darc cli — Arcade enforces a minimum version; if `darc` refuses with a "below the minimum required version" error, run `.\eng\common\darc-init.ps1`
## Phase Summary
| Phase | Trigger | Key Actions |
|---|---|---|
| **0: Instantiate** | User-initiated | Validate inputs, create GitHub tracking issue |
| **1: Branch & Prepare** | `BRANCH_SNAP_DATE` | Create `vs*` branch, DARC channel setup (batched PR), **audit all `vs*` branches for retirement**, `VisualStudio.ChannelName` |
| **2: DARC Subscription Updates** | Phase 1 branch exists (`vs*` created) | Retarget `main`-targeting subs + VMR backflow to next channel, retired-branch cleanup (batched PR), Arcade verify |
| **3: Bump Main** | Phase 2 merged | Branding PR in `main` (`VersionPrefix` → next, ApiCompat baseline, refresh OptProf baseline) |
| **4: Final Branding** | 7 days before `INSIDERS_SNAP_DATE` | Telemetry evidence checks, public API promotion, OptProf bootstrap, VS insertion before Insiders snap |
| **5: Post-GA** | VS shipped (`VS_SHIP_DATE`) | Resolve the exact shipped version (SDK-coupled? SDK wins over VS `rel/stable`), nuget.org publish, docs, GitHub release, Change Waves Learn sync, retro |
## DARC Batching
DARC write commands push to the [maestro-configuration](https://dev.azure.com/dnceng/internal/_git/maestro-configuration) repo. Batch related changes into **one PR**:
1. Choose a branch name like `release/msbuild-{{THIS_RELEASE_VERSION}}`
2. Add `--configuration-branch <name> --no-pr` to every write command except the last
3. Last command: use `--configuration-branch <name>` without `--no-pr` to create the PR
4. Get the PR reviewed and merged
Read-only commands (`get-default-channels`, `get-subscriptions`, `get-channel`) don't need these flags.
**Non-interactive (`-q`).** `darc add-default-channel` / `add-subscription` prompt `y/n` when the target branch does not exist yet (e.g. pre-creating the `vs{{NEXT_VERSION}}` mapping in Phase 1.2c, or adding the new `vs{{THIS_RELEASE_VERSION}}` backflow in Phase 2). `darc delete-subscriptions` always prompts for confirmation. Console input is redirected in an agent session, so the prompt **fails or hangs the command** — always pass `-q` for these writes. Note the verb is `delete-subscriptions` (plural); `delete-subscription` does not exist.
**Phase 2 — what moves vs. what stays.** When rotating `main` to the next channel, retarget **only** the subscriptions whose **target branch is `main`** (`dotnet/dotnet @ main`, `dotnet/fsharp @ main`). **Never** retarget a subscription that targets a VMR servicing/release branch (`dotnet/dotnet @ release/*`) — that includes the SDK band paired with the new `vs{{THIS_RELEASE_VERSION}}` branch and any `.NET-next` preview band (`release/*-preview*`). Those stay on `VS {{THIS_RELEASE_VERSION}}` so the new release branch owns their downstream flow; moving them steals it. (This bit the 18.9 release: the band and preview subs were moved and had to be reverted.)
**Phase 2 — VMR backflow rotation (easy to miss).** Backflow (`dotnet/dotnet → msbuild`, source-enabled) must rotate too **when the new `vs{{THIS_RELEASE_VERSION}}` is paired with an SDK band** (skip for a VS-only release): repoint the `→ main` backflow to the **next** SDK band channel (`.NET <NEXT_BAND> SDK`, the channel `dotnet/dotnet @ main` publishes to), and **add** a backflow from the **outgoing** band channel into the new `vs{{THIS_RELEASE_VERSION}}` branch (mirror the prior release branch's backflow, e.g. `vs18.0 ← .NET 10.0.1xx SDK`). See checklist steps 2.2b / 2.3f / 2.3g.
## Branch retirement (Phase 1.3)
Each release retires the `vs*` branches that have fallen out of support. **This is an audit of every live branch, not a check of one candidate.** The rule of thumb (`vs{{THIS_RELEASE_VERSION}} - 3`) identifies only the *newest* candidate; nothing in the process ever revisits older branches, so a single missed retirement persists forever and the backlog grows every month.
**Two lifecycles, two sources — you need both.** Neither one decides a branch on its own:
| Lifecycle | Source |
|---|---|
| **SDK band** | [supported .NET versions table](https://learn.microsoft.com/dotnet/core/porting/versioning-sdk-msbuild-vs#supported-net-versions) — maps each feature band to its VS version and support end date |
| **Visual Studio** | [VS Product Lifecycle and Servicing](https://learn.microsoft.com/visualstudio/releases/2026/servicing-vs) — annual releases and the LTSC table |
**Long-lived VS versions — hardcoded, don't re-derive these each release.** These keep their branches alive for years:
| Visual Studio | MSBuild branch | Supported until |
|---|---|---|
| Visual Studio 2022 | `vs17.14` | January 2032 |
| Visual Studio 2019 | `vs16.11` | April 2029 |
| Visual Studio 2017 | `vs15.9` | April 2027 |
**VS 2026 and later use an annual model: 2 years per release** — one year of monthly feature updates, then one security-only year on the LTSC. Only the **LTSC baseline version** gets that second year; an ordinary monthly release falls out of support as soon as the next monthly ships. So each year exactly one `vs18.x` becomes the LTSC and must be kept ~2 years while its neighbours retire quickly — identify it from the LTSC table on the servicing page (`2026-LTSC` ends **November 9, 2027**).
⚠️ **A VS LTSC can expire before the SDK band that shipped with it** — `2026-LTSC` ends Nov 2027, but .NET 10 (LTS) runs to Nov 2028. So the SDK side is frequently what keeps a branch alive, which is why `vs18.0` stays despite being the oldest branch.
🛑 **Maestro is not a lifecycle source.** A `.NET X.Y.Zxx SDK` channel — including `... SDK Release` variants — persists long after the band dies. Reasoning "the channel still exists, so the band is supported" is invalid and is exactly how `vs18.6` (band 10.0.3xx, EOL Aug 2026) was missed during the 18.11 release and had to be retired afterwards.
Apply the **combined rule**: a branch paired with both an SDK band and a VS version retires only when *both* lifecycles agree. Two mechanical red flags find most of the backlog:
| Signal | Meaning |
|---|---|
| Branch's `VS X.Y` channel has **no outbound subscription** | Nothing consumes it — the branch feeds nothing. Compare a live one: `vs18.0 → dotnet/dotnet release/10.0.1xx`. |
| Default channel exists for a **branch that isn't in the repo** | Pure orphan — delete the mapping. |
Worked example from 18.11: `vs18.0` pairs with 10.0.1xx (EOL Nov 2028) so it is **kept despite being the oldest branch**; `vs18.6` (10.0.3xx, EOL Aug 2026) retires; `vs18.4` and `vs18.5` were orphaned default channels for deleted branches.
## Executing a Phase
When asked to execute a specific phase:
1. Read the full phase from `documentation/release-checklist.md`
2. Verify the trigger condition is met (previous phases completed)
3. Execute steps in order — respect sequential/parallel annotations
4. For DARC commands: batch writes into one configuration PR per phase
5. Record all output URLs in the tracking issue's artifact table
6. Mark checkboxes as completed in the tracking issue
7. In **Phase 4**, complete telemetry checkpoints 4.0 and 4.4a using the shared evidence procedure in `documentation/release-checklist.md`.
8. In **Phase 4** (step 4.7): if `documentation/wiki/ChangeWaves.md` is changed for this release, update the public Learn page at https://learn.microsoft.com/visualstudio/msbuild/change-waves. Sync the Change Waves Learn page from `documentation/wiki/ChangeWaves.md` on the `vsXX.Y` branch that is live in VS Insiders / the latest preview SDK. PR goes to `MicrosoftDocs/visualstudio-docs-pr` (`docs/msbuild/change-waves.md`); example: https://github.com/MicrosoftDocs/visualstudio-docs-pr/pull/15662.
## Key Files
| File | Purpose |
|---|---|
| [`documentation/release-checklist.md`](../../../documentation/release-checklist.md) | **Operational checklist** — the source of truth |
| [`documentation/release.md`](../../../documentation/release.md) | Process description: final branding, public API, major version steps |
| [`documentation/wiki/ChangeWaves.md`](../../../documentation/wiki/ChangeWaves.md) | Source doc for the Learn page sync — always sync the `vsXX.Y` (Insiders/preview-SDK) copy, not `main` |
| [MSBuild Change Waves Learn page](https://learn.microsoft.com/visualstudio/msbuild/change-waves) | Public docs target to [`MicrosoftDocs/visualstudio-docs-pr`](https://github.com/MicrosoftDocs/visualstudio-docs-pr) (`docs/msbuild/change-waves.md`) |
| [`eng/Versions.props`](../../../eng/Versions.props) | `VersionPrefix`, `PackageValidationBaselineVersion`, `BootstrapSdkVersion` |
View on GitHub