dotnet / dotnet/msbuild

MSBuild Release Checklist 18.10

Open
#14,562 0 comments 0 reactions 2 assignees Claimed by @VolPlita View on GitHub
Area: Our Own Build triaged
Dominant language
C#
Stars
5.5k
Forks
1.5k
Avg merge
1d 8h
Merged PRs (30d)
141

Description

> **Phase 0 complete.** Inputs resolved from `eng/Versions.props` (`VersionPrefix` = `18.10.0`), `.config/git-merge-flow-config.jsonc`, `darc get-channels`, and the DevDiv wiki ([VS Dates](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/49807/VS-Dates), [Dev18.10 Schedule](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki?pagePath=%2FProduct%20Lifecycle%20and%20Release%2FDev18%20Release%2FDev18.10%20Schedule)).
>
> Verified at instantiation: `vs18.10` does **not** exist on `origin`; DARC channels `VS 18.10` (10191) and `VS 18.11` (10800) both already exist.
>
> ⚠️ **Schedule is tight.** Branch snap (2026-07-29) and the VS `main` → `rel/insiders` snap (2026-07-31) are ~2 days apart, so Phase 4's "7 days before insiders snap" trigger cannot be met. Phases 1–4 must run back-to-back. (Same shape as 18.9: branched 2026-06-30.)

---

## Release Output

Artifacts produced over the course of the release. Record each URL here as the corresponding phase completes so this issue serves as the single index back into every PR / build / tag that defines `{{THIS_RELEASE_EXACT_VERSION}}`.

| Artifact | URL |
|---|---|
| Phase 1.2d — maestro-configuration PR (channels for `18.10` / `18.11`) | https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/63336 |
| Phase 1.1 — `vs18.10` branch | https://github.com/dotnet/msbuild/tree/vs18.10 (created at `c88db8eb054510123cf4511ae3398225099a598f`) |
| Phase 1.3 — merge-flow config PR (add `vs18.10`, retire `vs18.7`) | https://github.com/dotnet/msbuild/pull/14563 |
| Phase 2.3j — maestro-configuration PR (`main`-targeting subs + VMR backflow retargeted, retired-branch cleanup) | https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/63340 |
| Phase 3.5 — `main` next-version main-bump PR | https://github.com/dotnet/msbuild/pull/14564 ✅ merged |
| Phase 4.4 — VS insertion PR | _TBD_ |
| Phase 5.1b — official build that produced `18.10.1` | https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=15116659 (`20260827.6` @ `3cd27c13eb422ceea9de6fb84d12684e3bcf3188`) |
| Phase 5.1c — `PackageArtifacts/Release` drop | https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=15116659&view=artifacts&pathAsName=false&type=publishedArtifacts |
| Phase 5.3 — GitHub release tag | https://github.com/dotnet/msbuild/releases/tag/v18.10.1 (tag `v18.10.1` → `3cd27c13eb422ceea9de6fb84d12684e3bcf3188`) |
| Phase 5.5 — maestro-configuration PR (add missing `VS 18.11` → `dotnet/fsharp release/dev18.11` subscription) | https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/65083 ✅ merged |

---

## Inputs

Fill in these values before starting. Version increments are irregular — they must be specified explicitly.

| Placeholder | Description | Value |
|---|---|---|
| `PREVIOUS_RELEASE_VERSION` | Version being replaced as latest | `18.9` |
| `THIS_RELEASE_VERSION` | Version being released now | `18.10` |
| `THIS_RELEASE_EXACT_VERSION` | The `VersionPrefix` that **actually shipped** to customers — read it from VS `rel/stable` (see Phase 5.1a), **not** assumed. It is usually `18.10.0`, but an OptProf-driven insertion bump can make the shipped version `18.10.1` or higher (e.g. 18.7 shipped as `18.7.1`). **Not known when first instantiating this checklist — leave blank until Phase 5.1a confirms it.** | **`18.10.1`** — resolved 2026-09-08 in Phase 5.1a from VS `rel/stable` (not `18.10.0`) |
| `NEXT_VERSION` | Version that main will be bumped to | `18.11` |
| `BRANCH_SNAP_DATE` | Date we create `vs18.10` from `main`. | `2026-07-29` (Wed) |
| `INSIDERS_SNAP_DATE` | Date VS snaps `main` → `rel/insiders`. Final-branded MSBuild must be in VS `main` **before** this date. From [VS-Dates wiki](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/49807/VS-Dates) | `2026-07-31` (Fri) |
| `STABLE_SNAP_DATE` | Date VS snaps `rel/insiders` → `rel/stable`. From [VS-Dates wiki](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/49807/VS-Dates) | `2026-08-25` (Tue) |
| `VS_SHIP_DATE` | Date VS ships publicly (GA). Post-GA tasks (nuget.org, docs) happen after this. | `2026-09-08` (Tue) |
| `PACKAGE_VALIDATION_BASELINE_VERSION` | Latest `18.10.0-preview-NNNNN-NN` MSBuild build reachable from `vs18.10`. Used as the ApiCompat baseline for the bumped `main`. **How to determine it:** see the [release skill](https://github.com/dotnet/msbuild/blob/main/.github/skills/release/SKILL.md#how-to-determine-package_validation_baseline_version). | `18.10.0-1.26378.2` |

**Derived values** (do not edit — computed from inputs):
- Release branch: `vs18.10`
- DARC channel: `VS 18.10`
- Next DARC channel: `VS 18.11`
- VS insertion target: VS `main` (VS snaps main → insiders → stable on its own schedule)

## Phase 0: Instantiate Release

> **Trigger**: User decides to start a release. Do this first.

- [x] Validate inputs:
- [x] Confirm `eng/Versions.props` on `main` has `VersionPrefix` = `18.10.0` — if not, the inputs are wrong
- [x] Confirm branch `vs18.10` does **not** already exist — if it does, this release was already started
- [x] Confirm DARC channel `VS 18.10` exists: \
`darc get-channels`\
If missing, it should have been created during the previous release (Phase 1.2b "create next channel" step). Create it now: `darc add-channel --name "VS 18.10"`
- [x] Create this tracking issue in dotnet/msbuild with all `{{PLACEHOLDERS}}` replaced
- [x] As phases complete, record artifact URLs in the **Release Output** table at the top of this checklist.

---

## Phase 1: Branch & Prepare

> **Trigger**: `2026-07-29` reached.

Steps are **sequential** — complete in order.

- [x] **1.0** **Pre-snap team check.** Before snapping the branch, ping the MSBuild team to confirm there is nothing they still need to merge into `main` that should ship in `18.10`. Anything that lands in `main` after Phase 1.1 will go into `18.11` instead.
- [x] **1.1** Create branch `vs18.10` from HEAD of `main` (**requires repo admin rights** — `git push` to `refs/heads/vs*` is restricted; if you don't have permission, ask a repo admin with `vs*` push rights to do it): \
`git push upstream HEAD:refs/heads/vs18.10`
✅ Created 2026-07-29 at `c88db8eb054510123cf4511ae3398225099a598f` (HEAD of `main`, _Enlighten ResolveKeySource for multithreaded mode (#13623)_). Note: `vs*` ref creation is blocked by repository ruleset 17801 (creation restricted + linear history); needs an admin/bypass actor, `maintain` is not enough. A `git push --dry-run` does **not** evaluate rulesets, so it falsely reports success.
- _If branched too early_ (main has commits that shouldn't be in the release): fast-forward the branch to the correct commit (the one currently inserted into VS main): \
`git push upstream :refs/heads/vs18.10`
- [x] **1.2** DARC configuration — batch all channel/mapping changes into **one PR** on the [maestro-configuration](https://dev.azure.com/dnceng/internal/_git/maestro-configuration) repo. \
Use `--configuration-branch msbuild-18.10` on every command and `--no-pr` on all but the last:
- [x] **1.2a** Ensure branch-to-channel association exists: \
First check: `darc get-default-channels --channel "VS 18.10" --branch vs18.10 --source-repo https://github.com/dotnet/msbuild` \
✅ Already existed as `(7029) dotnet/msbuild @ vs18.10 -> VS 18.10` (pre-created by the 18.9 release), so this was a no-op.
If `No matching channels were found.`: \
`darc add-default-channel --channel "VS 18.10" --branch vs18.10 --repo https://github.com/dotnet/msbuild --configuration-branch msbuild-18.10 --no-pr`
- [x] **1.2b** Create DARC channel for **next** release: \
`darc add-channel --name "VS 18.11" --configuration-branch msbuild-18.10 --no-pr` \
_(If channel already exists, this is a no-op.)_ ✅ Already existed as `(10800) VS 18.11` (pre-created by the 18.9 release) — no-op.
- [x] **1.2c** Pre-create default channel mapping for the **next** release branch (**last command — omit `--no-pr` to create the PR**). The `vs18.11` branch does not exist yet, so pass `-q` (non-interactive) to skip the "branch doesn't exist" prompt — otherwise the command blocks/aborts: \
`darc add-default-channel --channel "VS 18.11" --branch vs18.11 --repo https://github.com/dotnet/msbuild --configuration-branch msbuild-18.10 -q`
✅ Ran 2026-07-29. This was the **only** write in Phase 1.2 (1.2a and 1.2b were already satisfied), so it created the PR on its own.
- [x] **1.2d** Get the maestro-configuration PR reviewed and merged: https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/63336
- [x] **1.3** Update `.config/git-merge-flow-config.jsonc`:
- [x] **1.3a** Insert `vs18.10` as the last entry before `main` in the merge chain. Add a comment noting the VS/SDK version context.
- [x] **1.3b** **Retire predecessor branches that will no longer be supported.** Remove their `MergeToBranch` entries and rewire the chain to skip them so automation does not open stale forward-merge PRs. \
How to identify a retired branch:
- **The combined rule:** a branch paired with both an SDK band and a VS version is retired **only when both lifecycles agree it is out of support**. If only one side says retired but the other is still supported, **keep the branch** — automation must still flow forward-merges so the still-supported lifecycle keeps receiving fixes.
- **SDK lifecycle** — for branches paired with an SDK band, check https://learn.microsoft.com/dotnet/core/porting/versioning-sdk-msbuild-vs#lifecycle. If the paired SDK band is past its support end date, the branch is SDK-retired.
- **VS lifecycle** — check the [VS Servicing Information wiki](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/27212/Visual-Studio-Servicing-Information). Rule of thumb: the VS support window covers the current release plus two preceding versions, so the first candidate for VS-retirement is `vs18.10 - 3` — **always confirm on the wiki**, since servicing exceptions can extend specific versions beyond the rule of thumb.
- **VS-only branches** (not paired with any active SDK band) are retired purely on the VS lifecycle.

---

## Phase 2: DARC Subscription Updates

> **Trigger**: `vs18.10` branch exists (Phase 1 complete). \
> **Why this runs before bumping `main`:** consumers of MSBuild via `main` (notably the VMR) should start receiving next-version bits from the `VS 18.11` channel **the moment `main` is bumped**. Reassigning `main`'s default channel **before** the Phase 3 branding bump means the first `main` build at the new version is already published to the correct channel; otherwise it lands on the now-stale `VS 18.10` channel.

### Phase 2 execution notes (2026-07-29)

**2.1 — forward subs on `VS 18.10`.** Three found; retargeted only the two whose target branch is `main`:

| Sub | Target | Action |
|---|---|---|
| `d1c6afb2-7bf1-43e4-8f2e-af7e0680b540` | `dotnet/dotnet` @ `main` | ✅ retargeted → `VS 18.11` |
| `c88fef6f-3728-44a8-80e6-587fb00258a1` | `dotnet/fsharp` @ `main` | ✅ retargeted → `VS 18.11` |
| `74ecf7c0-f253-4c7e-bc54-930d4583fba5` | `dotnet/dotnet` @ `release/11.0.1xx-preview7` | 🛑 **left on `VS 18.10`** — `.NET-next` preview band, per the do-not-touch rule. `vs18.10` now owns it. |

**2.2 — release branch association already existed** as `(7029) dotnet/msbuild @ vs18.10 -> VS 18.10` (pre-created by the 18.9 release), so **2.3d was skipped**.

**2.2b — VMR backflow: nothing to do.** `vs18.10` is a **VS-only** release with no paired SDK band (10.0.4xx pairs with 18.9; the next band had not opened).
- **2.3f skipped** — the `→ main` backflow `91fa59f1-1864-46b1-b482-87955691317c` is already on `.NET 11.0.1xx SDK`, which is exactly the channel `dotnet/dotnet @ main` publishes to (`6405`). Unchanged ⇒ no-op.
- **2.3g skipped** — confirmed against precedent: the previous VS-only release branches `vs18.7` and `vs18.8` have **only** an Arcade subscription and no `dotnet/dotnet` backflow.

**2.3e — retired-branch cleanup for `vs18.7`** (retired in 1.3b): deleted subscription `0d0c31fc-ed95-4201-b312-fa81f93928bb` (arcade → vs18.7) and default channel `(6921) vs18.7 -> VS 18.7`.

**2.4 / 2.3h — Arcade subscription for `vs18.10` was missing and has been added.** Channel **`.NET Eng - Latest`** (not `.NET 10 Eng`), everyWeek, failure tags `@dotnet/kitten`.

> ⚠️ **Process deviation to fold back into the checklist (Phase 5.6).** `documentation/release-checklist.md` step 2.4 says the Arcade channel is "the matching `.NET Eng` channel … determined by the .NET band the branch is paired with". A **VS-only** release branch has no paired band, so that rule gives no answer — and the sibling branches `vs18.6`–`vs18.9` all sit on `.NET 10 Eng`, which would be the misleading pattern-match. The correct channel for a VS-only release branch is **`.NET Eng - Latest`** (same as `main`). Checklist 2.4 should say so explicitly.

**NuGet subscription note (checklist aside): no action.** `nuget/nuget.client → msbuild @ main` (`72a0261a-25e2-43ab-b60d-34a46603b072`) is already on `.NET 11.0.1xx SDK`; the next-to-ship band did not rotate this release.

**Full Arcade coverage after this PR** — every branch in the merge chain has a subscription:
`main` → `.NET Eng - Latest` · `vs18.10` → `.NET Eng - Latest` · `vs18.9`/`vs18.8`/`vs18.6`/`vs18.0` → `.NET 10 Eng` · `vs17.14`/`vs17.12` → `.NET 9 Eng` · `vs17.11`/`vs17.8` → `.NET 8 Eng` · `vs16.11` → `.NET 6 Eng`

---
First, **gather information** (read-only queries — no PR needed):

- [x] **2.1** Identify the **forward-flow** subscriptions to retarget: \
`darc get-subscriptions --exact --source-repo https://github.com/dotnet/msbuild --channel "VS 18.10"` \
This lists every `msbuild → downstream` subscription currently on the outgoing channel. **Retarget ONLY the subscriptions whose _target branch_ is `main`** — normally `dotnet/dotnet @ main` (the VMR/SDK main) and `dotnet/fsharp @ main` (fsharp tracks the channel msbuild `main` publishes to). Record their IDs. \
🛑 **Do NOT touch subscriptions that target a VMR servicing/release branch** (`dotnet/dotnet @ release/*`). That includes the SDK band now paired with `vs18.10` (it is now fed by `vs18.10` via the `VS 18.10` channel) **and** any `.NET-next` preview band (`release/*-preview*`). Leaving them on `VS 18.10` is what lets the new release branch own its downstream flow; moving them would steal it. The single rule: **retarget a forward sub only if its target branch is `main`.**
- [x] **2.2** Verify release branch channel association: \
`darc get-default-channels --source-repo https://github.com/dotnet/msbuild --branch vs18.10` \
Note whether the association exists (needed for step 2.3d).
- [x] **2.2b** **(VMR backflow — do this only if `vs18.10` is paired with an SDK band that `main` was feeding; skip entirely for a VS-only release with no SDK band.)** Identify the backflow subscriptions (VMR → msbuild, source-enabled) and the band channels: \
`darc get-subscriptions --target-repo https://github.com/dotnet/msbuild --target-branch main --source-repo https://github.com/dotnet/dotnet` → record the source-enabled `→ main` backflow **ID** (for 2.3f). \
`darc get-default-channels --source-repo https://github.com/dotnet/dotnet --branch main` → the **next** SDK band channel `main` now publishes to, e.g. `.NET SDK`. Compare to the current `→ main` backflow channel — if unchanged, 2.3f is a no-op (for 2.3f). \
`darc get-default-channels --source-repo https://github.com/dotnet/dotnet --branch release/` → the **outgoing** band channel that `vs18.10` now owns, e.g. `.NET SDK` (for 2.3g).

Then, **batch all write operations into one PR** on the [maestro-configuration](https://dev.azure.com/dnceng/internal/_git/maestro-configuration) repo. \
Use `--configuration-branch msbuild-18.10-main-bump` (distinct from the Phase 1 channel branch) and `--no-pr` on all but the last command. \
_Tip: `darc add-default-channel` / `add-subscription` prompt interactively when the target branch does not exist yet; pass `-q` (non-interactive) to skip that prompt._

- [x] **2.3** DARC channel/subscription updates:
- [x] **2.3a** Remove main → old channel mapping: \
`darc delete-default-channel --repo https://github.com/dotnet/msbuild --branch main --channel "VS 18.10" --configuration-branch msbuild-18.10-main-bump --no-pr`
- [x] **2.3b** Associate main with next channel: \
`darc add-default-channel --channel "VS 18.11" --branch main --repo https://github.com/dotnet/msbuild --configuration-branch msbuild-18.10-main-bump --no-pr`
- [x] **2.3c** Retarget **each** `main`-targeting forward subscription from 2.1 to the next channel — run once per ID (typically `dotnet/dotnet @ main` and `dotnet/fsharp @ main`): \
`darc update-subscription --id --channel "VS 18.11" --configuration-branch msbuild-18.10-main-bump --no-pr`
- [x] **2.3d** If release branch association was missing in 2.2, add it: \
`darc add-default-channel --channel "VS 18.10" --branch vs18.10 --repo https://github.com/dotnet/msbuild --configuration-branch msbuild-18.10-main-bump --no-pr`
- [x] **2.3e** **Delete subscriptions for retired branches.** For each branch identified as retired in step 1.3b (apply the same combined SDK+VS rule — do **not** delete subscriptions for a branch that's retired on only one side, since fixes must keep flowing into the still-supported lifecycle), remove its inbound subscriptions and any default channel associations.
List them: `darc get-subscriptions --target-repo https://github.com/dotnet/msbuild --target-branch ` \
Delete each: `darc delete-subscription --id --configuration-branch msbuild-18.10-main-bump --no-pr`
- [x] **2.3f** **(VMR backflow — skip for a VS-only release, or if 2.2b found the channel unchanged.)** Repoint the `→ main` backflow (ID from 2.2b) to the **next** SDK band channel so the bumped `main` pulls next-version VMR dependencies: \
`darc update-subscription --id --channel ".NET SDK" --configuration-branch msbuild-18.10-main-bump --no-pr`
- [x] **2.3g** **(VMR backflow — skip for a VS-only release.)** Add a backflow from the **outgoing** SDK band into the new release branch so that band keeps flowing into `vs18.10` (mirrors the previous release branch's backflow: source-enabled, source dir `msbuild`, everyDay, Standard merge, excluded assets `*`; the branch is brand-new so pass `-q`): \
`darc add-subscription --channel ".NET SDK" --source-repo https://github.com/dotnet/dotnet --target-repo https://github.com/dotnet/msbuild --target-branch vs18.10 --update-frequency everyDay --source-enabled --source-directory msbuild --excluded-assets '*' --standard-automerge --configuration-branch msbuild-18.10-main-bump --no-pr -q`
- [x] **2.3h** **Arcade fix-up (run 2.4 first if you haven't).** _If the Arcade subscription from 2.4 below is missing or pointed at the wrong channel, include the fix-up here with `--no-pr` before creating the PR._
- [x] **2.3i** **Create the PR** — re-run the final write command without `--no-pr` to open the PR on the configuration branch.
- [ ] **2.3j** Get the maestro-configuration PR reviewed and merged: https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/63340

Verifications (**parallel** — read-only, no ordering dependency):

- [x] **2.4** Verify the Arcade subscription for `vs18.10`: \
`darc get-subscriptions --exact --target-repo https://github.com/dotnet/msbuild --source-repo https://github.com/dotnet/arcade`
- **Every supported branch must have an Arcade subscription** from the matching `.NET Eng` channel (the channel is determined by the .NET band the branch is paired with — e.g. a branch paired with .NET 10 subscribes to `.NET 10 Eng`).
> _Roslyn subscription verification intentionally omitted from the per-release checklist: there is always exactly one Roslyn subscription, targeting `main` only and its channel does not rotate with SDK bands._
>
> **NuGet subscription:** when the next-to-ship SDK band rotates (e.g. `4xx` → `5xx`), the NuGet → `msbuild/main` subscription must be re-pointed to the new band's channel.

---

## Phase 3: Bump Main & Update Pipelines

> **Trigger**: Phase 2 DARC updates merged (`main`'s default channel is now `VS 18.11`).

### Phase 3 execution notes (2026-07-29)

PR **https://github.com/dotnet/msbuild/pull/14564** — opened as a **draft** so it cannot merge before the Phase 2 DARC PR ([63340](https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/63340)). Flip to *ready for review* once 63340 is merged.

| Step | Value |
|---|---|
| 3.1 `VersionPrefix` | `18.10.0` → **`18.11.0`** |
| 3.2 `PackageValidationBaselineVersion` | **`18.10.0-1.26378.2`** (build 9434 run `20260728.2`, sha `bf5e076cdd`, on dotnet-tools feed, reachable from `vs18.10`) |
| 3.3 `OptProfBaselineDrop` | **`OptimizationData/DotNet-msbuild-Trusted/main/20260724.1/14773990/1`** |
| 3.4 `CompatibilitySuppressions.xml` | regenerated (4 files) — **was required this release** |

**3.4 was not optional this time.** Changing the baseline from 18.9 to 18.10 made the baseline-specific `CP0001`/`CP0008`/`PKV006` suppressions unnecessary, which the build reports as an error. Only those entries were removed; the unrelated `PKV004` framework suppressions are intact. Verified by a clean pack (0 errors / 0 warnings) **and** by a control pack of unmodified `main` (also 0 errors), proving the churn is attributable to the baseline change and is not masking a real break.

> 🐞 **`scripts/Get-PackageValidationBaseline.ps1` is stale — must be fixed before the 18.11 release (Phase 5.6).**
> It failed with `No eligible candidate builds found for 18.10`. Two independent defects:
> 1. **Wrong package format.** It hardcodes `"$ThisReleaseVersion.0-preview-$shortDate-$revStr"`, but `PreReleaseVersionLabel` in `eng/Versions.props` is now `1`, so official builds publish as `18.10.0-1.26378.2`, not `18.10.0-preview-26378-02`. On the dotnet-tools feed the last `18.10.0-preview-*` was published 2026-07-10; everything after is `18.10.0-1.*`.
> 2. **Over-strict reachability.** It only accepts a build at the *exact* `git merge-base` commit. A branch cut shortly after a commit has no completed build at that sha, so the lookup finds nothing. The documented rule is "reachable from `vs`", which admits any ancestor commit.
>
> `documentation/release-checklist.md` (Phase 3.2) and `.github/skills/release/SKILL.md` ("How to determine `PACKAGE_VALIDATION_BASELINE_VERSION`") also document the obsolete `-preview-NNNNN-NN` format and need the same correction.

> 📄 **Also for Phase 5.6:** `AGENTS.md` documents the whole-repo build as `.\build.cmd -v quiet`, but `-v` is not a recognized alias — `quiet` gets forwarded as a project path and the build dies with `MSB1008: Only one project can be specified`. The working form is `.\build.cmd -verbosity quiet`.

---
Create **one PR in `main`** containing all of the following changes:

- [x] **3.1** `eng/Versions.props`: Update `VersionPrefix` to `18.11.0`
- [x] **3.2** `eng/Versions.props`: Update `PackageValidationBaselineVersion` to `{{PACKAGE_VALIDATION_BASELINE_VERSION}}`. \
Resolve it deterministically with `pwsh ./scripts/Get-PackageValidationBaseline.ps1 -ThisReleaseVersion 18.10` (requires `az login` with devdiv access). See [How to determine `PACKAGE_VALIDATION_BASELINE_VERSION`](https://github.com/dotnet/msbuild/blob/main/.github/skills/release/SKILL.md#how-to-determine-package_validation_baseline_version) in the release skill for the manual fallback.
- [x] **3.3** `.vsts-dotnet.yml`: Refresh the hardcoded OptProf baseline so the **next** `vs*` branch cut from `main` inherits valid OptProf data (this is what lets that branch's first official build succeed without the manual Phase 4.4 rerun). \
Resolve the current value with `pwsh ./scripts/Get-LatestOptProfDrop.ps1` (requires `az login` with devdiv access), then set it as `OptProfBaselineDrop`: \
`//1'`.
- [x] **3.4** If the build pipeline fails on API-compat (only then — this step is a fix-up, not a routine action), update `CompatibilitySuppressions.xml` files. Run: \
`dotnet pack MSBuild.Dev.slnf /p:ApiCompatGenerateSuppressionFile=true` \
See [API compat documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/apicompat/overview) for details.
- [x] **3.5** Merge main-bump PR: https://github.com/dotnet/msbuild/pull/14564

---

## Phase 4: Final Branding & VS Insertion

> **Trigger**: 7 calendar days before `2026-07-31`. \
> **Precondition**: Phases 1–3 complete. Preview builds from `vs18.10` have been inserting into VS `main` since Phase 2. \
> **Goal**: Final-brand the release branch and get the final-branded bits inserted into VS `main` before VS snaps to `rel/insiders`.

Steps are **sequential**.

### Phase 4 execution notes (2026-07-29)

**4.1 — Public API promotion: no-op.** There is nothing to promote: the repo contains **no `PublicAPI.*` files at all** (`git ls-files | grep PublicAPI` → 0 results on `vs18.10`). The step as written in the checklist ("move contents of `PublicAPI.Unshipped.txt` → `PublicAPI.Shipped.txt`") no longer applies to this repo.

**4.2 — OptProf bootstrap: cannot be confirmed yet.** The auto-triggered official build on `vs18.10` ([20260729.5](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=14802678)) failed, but **due to an infrastructure outage, not OptProf** — every job failed at `Initialize job` with no log attached and no error issue recorded, i.e. the jobs never reached an agent. This is a different signature from the genuine OptProf failure seen on `vs18.9`'s first build (`AcquireOptimizationData.targets(74,5): error`). No action taken; re-evaluate once the outage clears.

> ⚠️ **`vs18.10` inherited a 5-week-old OptProf drop — watch 4.2 when builds resume.**
> `vs18.10` was cut from `main` **before** the Phase 3.3 refresh landed, so it carries the drop from the *previous* release cycle:
> - `vs18.10` has `OptimizationData/DotNet-msbuild-Trusted/main/20260623.5/14471019/1` (23 Jun)
> - `main` now has `OptimizationData/DotNet-msbuild-Trusted/main/20260724.1/14773990/1` (24 Jul, set by #14564)
>
> This is inherent to the phase ordering — 3.3 refreshes `main` for the benefit of the *next* branch cut (`vs18.11`), not the branch just cut. If the first clean official build on `vs18.10` fails in `AcquireOptimizationData`, that is the real 4.2 case: cancel it and re-run with the `Optional OptProfDrop Override` set to `main`'s current drop above (checklist 4.2a/4.2b).
>
> Worth considering for Phase 5.6: refreshing `OptProfBaselineDrop` **on the release branch** (or refreshing `main` *before* the branch cut) would remove this inherited-staleness gap entirely.

**4.3 — M2/QB approval: skip unless we slip.** Only required if the insertion misses `2026-07-31`.

**4.4 — VS insertion: no pipeline change required.** Verified `azure-pipelines/vs-insertion.yml` on `vs18.10`: with `TargetBranch: auto` the pipeline sets `InsertTargetBranch` to `main`, which is already the correct Phase 4.4 target. No `AutoInsertTargetBranch` edit is needed for this phase.

**4.5 / 4.6 — skipped.** Regular release with no servicing commit to backport; `AutoInsertTargetBranch` stays untouched.

> ⏱️ **Schedule risk.** VS snaps `main` → `rel/insiders` on **2026-07-31**, ~2 days out. The insertion must land in VS `main` before then, and it is currently gated on the official build succeeding once the outage clears.

---

- [x] **4.1** Promote public API on `vs18.10` branch: \
Move contents of `PublicAPI.Unshipped.txt` → `PublicAPI.Shipped.txt` for all projects with API changes. See [release.md](https://github.com/dotnet/msbuild/blob/main/documentation/release.md) for details.
- [x] **4.2** Bootstrap OptProf for `vs18.10`. **If the Phase 3.3 hardcoded `OptProfBaselineDrop` was kept current, the auto-triggered build should already pick it up (`.vsts-dotnet.yml` seeds `OptProfDrop` from it on `vs*` branches) and this step is a no-op.** Only if the official build still fails for lack of OptProf data (e.g. the baseline was stale/empty at branch-cut):
- [ ] **4.2a** **Cancel** the auto-triggered official build for `vs18.10`.
- [ ] **4.2b** **Re-run the official build manually** for `vs18.10` with the OptProf override from `main` — set `Optional OptProfDrop Override` to `main`'s latest OptProf drop path (`pwsh ./scripts/Get-LatestOptProfDrop.ps1`).
- [ ] **4.3** Get M2 or QB approval as necessary per the VS schedule. \
_**Only required if we are behind the VS schedule** — i.e. the insertion didn't land in VS `main` before `2026-07-31` (4.4 was missed) and a milestone-gate approval is now needed. If the insertion made the schedule, **skip this step**._
- [ ] **4.4** Babysit the VS insertion PR from `vs18.10` into VS `main` (auto-generated at https://devdiv.visualstudio.com/DevDiv/_git/VS/pullrequests). The inserted bits must be in VS `main` **before** `2026-07-31` so they are included when VS snaps to `rel/insiders`: _TBD_ \
The insertion PR contains the inserted package versions — useful for the nuget.org publishing step.

**After insiders snap** (only if a backport to insiders is needed):

> 🛑 **4.5 and 4.6 are NOT part of the regular release flow — skip them entirely on a normal release.** \
> They only apply when **servicing** a previously-shipped release (i.e. you actually have a hotfix commit on `vs18.10` that needs to be inserted into VS's already-snapped `rel/insiders` or `rel/stable` branch). If you have no such commit to service, leave `AutoInsertTargetBranch` untouched and move on to Phase 5.
>
> ⚠️ When you *do* need to service: re-confirm which VS branch you actually want to insert into before flipping `AutoInsertTargetBranch`. The default is `main`, so forgetting to retarget after the snap silently lands your fix in the next VS instead of the one you're servicing.

- [ ] **4.5** Update [`azure-pipelines/vs-insertion.yml`](https://github.com/dotnet/msbuild/blob/main/azure-pipelines/vs-insertion.yml): retarget `AutoInsertTargetBranch` for `vs18.10` from VS `main` → `rel/insiders`. This enables direct insertion of hotfix commits into the insiders branch.

**After stable snap** (only if a backport to stable is needed):

- [ ] **4.6** Update [`azure-pipelines/vs-insertion.yml`](https://github.com/dotnet/msbuild/blob/main/azure-pipelines/vs-insertion.yml): retarget `AutoInsertTargetBranch` for `vs18.10` → `rel/stable`. This enables direct insertion of hotfix commits into the stable branch.

---

## Phase 5: Post-GA

> **Trigger**: `2026-09-08` has passed and VS release has shipped.

Steps are **mostly parallel** unless noted.

- [ ] **5.1** Push packages to nuget.org.

> **How publishing works:** We don't push packages ourselves. We hand a link to the **Release** artifacts of the official build to the _.NET Release Team_, and they push to nuget.org. Searching past mail for the subject _"Publish MSBuild 18.10 to NuGet.org" for the template.

- [x] **5.1a** Determine the exact MSBuild version that actually shipped to customers. → **`18.10.1`** \
_**Not an SDK-coupled release → VS `rel/stable` is the source of truth** (second bullet of this step). `vs18.10` is a **VS-only** branch: it has no source-enabled subscription from any `.NET SDK` channel and no codeflow into the VMR at all. Verified 2026-09-08 by inspecting the live `dotnet/dotnet` codeflow PRs and resolving each source commit to its MSBuild branch — `main` → `dotnet/dotnet` `main` + `release/11.0.1xx`, `vs18.9` → `release/10.0.4xx`, `vs18.0` → `release/10.0.1xx`; **no VMR branch is fed by `vs18.10`**. (`darc` was unavailable in this session, so the subscription map was derived from the codeflow PRs instead of `darc get-subscriptions`.)_ \
_VS `rel/stable` [`.corext/Configs/msbuild-components.json`](https://devdiv.visualstudio.com/DevDiv/_git/VS?path=/.corext/Configs/msbuild-components.json&version=GBrel/stable) has `Microsoft.Build` = `18.10.1-1.26427.6+3cd27c13eb422ceea9de6fb84d12684e3bcf3188` (drop `vs18.10/20260827.6`) → numeric `VersionPrefix` = **`18.10.1`**. ⚠️ **Not `18.10.0`** — an insertion bump moved it, exactly the case this step warns about. `vs18.10` has since advanced past this commit (later builds `20260831.12`, `20260903.1`, `20260907.2` exist), and **those have not shipped**._
- **If this release is coupled with an SDK release: use the SDK as the source of truth**. Look up the MSBuild version baked into the shipped SDK build.
- Otherwise, read the **authoritative GA'd value from VS `rel/stable`**: the `Microsoft.Build` component version in [`.corext/Configs/msbuild-components.json`](https://devdiv.visualstudio.com/DevDiv/_git/VS?path=/.corext/Configs/msbuild-components.json&version=GBrel/stable) (e.g. `18.7.1-servicing-NNNNN-NN+`). Extract just the **numeric `VersionPrefix`** from that string — drop the `-servicing-NNNNN-NN+` suffix — and use it as `{{THIS_RELEASE_EXACT_VERSION}}` (e.g. `18.7.1`). **Do not** rely solely on the VS insertion PR — that PR targets VS `main` and can be superseded by a later servicing insertion before GA, whereas `rel/stable` reflects what actually shipped.
- [x] **5.1b** In the [MSBuild official build pipeline](https://devdiv.visualstudio.com/DevDiv/_build?definitionId=9434), filter to the `vs18.10` branch and locate the build whose output version matches the one identified in 5.1a (`18.10.1`). \
→ **build `20260827.6`, id `15116659`, succeeded, source version `3cd27c13eb422ceea9de6fb84d12684e3bcf3188`**: https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=15116659 \
_Cross-checked against the `rel/stable` component drop path `DotNet-msbuild-Trusted/vs18.10/20260827.6` — same build. Commit confirmed to be on `origin/vs18.10`: "[vs18.10] Expand built-in metadata references in the task host (#14823)"._
- [x] **5.1c** From that build, open the **Publish Artifacts** step and grab the link to the **`PackageArtifacts/Release`** drop. Verify the **Release** folder contains all of: \
→ https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=15116659&view=artifacts&pathAsName=false&type=publishedArtifacts \
_Verified 2026-09-08 — all 7 release packages present at `18.10.1` under `PackageArtifacts/Release/`, and **only** those 7 (the `Shipping/`, `PreRelease/` and root folders carry the prerelease `18.10.1-1.26427.6` / `18.10.1-1.final` variants, which are **not** what gets published). The checklist's `PackageArtifacts/Release` path is correct for this release — the 18.9 note claiming the folder is `Shipping` no longer applies._
- [x] `Release/Microsoft.Build.Utilities.Core.18.10.1.nupkg`
- [x] `Release/Microsoft.Build.18.10.1.nupkg`
- [x] `Release/Microsoft.Build.Framework.18.10.1.nupkg`
- [x] `Release/Microsoft.Build.Runtime.18.10.1.nupkg`
- [x] `Release/Microsoft.Build.Tasks.Core.18.10.1.nupkg`
- [x] `Release/Microsoft.NET.StringTools.18.10.1.nupkg`
- [x] `Release/Microsoft.Build.Templates.18.10.1.nupkg`
- [x] **5.1d** Email the _.NET Release Team_ with the `Release` link from 5.1c and ask them to publish `18.10.1` to nuget.org. \
_⏳ **Action required by a human** — nothing is on nuget.org yet (verified 2026-09-08: `Microsoft.Build` latest published is `18.9.6`; no `18.10.x`). Mail subject template: "Publish MSBuild 18.10 to NuGet.org"._

- [x] **5.2** Publish docs

> **How publishing works:** The reference-publishing vendor team generates Microsoft Learn reference pages from the shipped MSBuild assemblies/xmldoc and then sends us a docs-repo PR with the regenerated content.

- [x] **5.2a** Create a reference-publishing ticket for the new release based on [this existing ticket](https://dev.azure.com/msft-skilling/Content/_workitems/edit/565854) as a template. Then wait for the vendor team to ping you with a link to the generated PR.
- ✅ Created 2026-09-08 for MSBuild 18.10 / package 18.10.1: https://dev.azure.com/msft-skilling/Content/_workitems/edit/632047 — waiting for the vendor-generated docs PR.
- [x] **5.2b** Review and approve the docs-repo PR the vendor team opens (example: [msbuild-api-docs#61](https://github.com/dotnet/msbuild-api-docs/pull/61)).
- ✅ Reviewed and approved: https://github.com/dotnet/msbuild-api-docs/pull/69
- [x] **5.3** Create GitHub release: ✅ _Done 2026-09-08 — **https://github.com/dotnet/msbuild/releases/tag/v18.10.1**_
- [x] **5.3a** **Precondition — confirm the previous release tag exists on `upstream`.** ✅ _`v18.9.6` exists on `dotnet/msbuild` (`b0e826c1f29e30dfe49535b4194f960cafbc3510`) with a published release. Note the previous release's **exact** version is `18.9.6`, not `18.9.0`._ \
`git fetch upstream --tags && git tag --list 'v18.9.6'` \
_(Assumes `upstream` is configured as the `dotnet/msbuild` remote. If not: `git remote add upstream https://github.com/dotnet/msbuild.git`.)_ \
If the tag is missing (e.g. the previous release was never tagged), create and push it **first**.
- [x] **5.3b** **Identify the commit to tag.** It is the source commit of the build identified in **5.1b** (the build that produced `18.10.1`). \
→ **`3cd27c13eb422ceea9de6fb84d12684e3bcf3188`** — _"[vs18.10] Expand built-in metadata references in the task host (#14823)", 2026-08-27, on `origin/vs18.10`; `eng/Versions.props` at that commit has `VersionPrefix` = `18.10.1`, independently corroborating the `rel/stable` reading from 5.1a._
- [x] **5.3c** Tag this release and push:
```
git tag v18.10.1 3cd27c13eb422ceea9de6fb84d12684e3bcf3188
git push origin v18.10.1
```
_✅ Done 2026-09-08 — tag `v18.10.1` → `3cd27c13eb422ceea9de6fb84d12684e3bcf3188`, pushed to `dotnet/msbuild`. Tagged the SHA directly instead of `git checkout && git tag` — identical result, but it avoids a detached-HEAD checkout of the working tree. Worth folding into the checklist text (5.6)._
- [x] **5.3d** Create release at https://github.com/dotnet/msbuild/releases/new — use `Generate Release Notes` to prepopulate. Tag `v18.10.1`, previous tag `v18.9.6`. \
_✅ Done 2026-09-08 — published (not draft, not prerelease): **https://github.com/dotnet/msbuild/releases/tag/v18.10.1**, notes auto-generated from `v18.9.6`._ \
_⚠️ **Expect a large, cross-branch-looking note set and do not "fix" it.** `v18.9.6` is on `vs18.9` and `v18.10.1` is on `vs18.10`, so the `v18.9.6..v18.10.1` range is **194 commits** and legitimately includes old servicing PRs (`vs16.11`/`vs17.8`/`vs17.12` arcade updates) that reached `vs18.10` via the `vs18.9 => vs18.10` merges but never reached `v18.9.6` itself. Verified the generated list matches `git log v18.9.6..v18.10.1` exactly._ \
_ℹ️ The release shows `targetCommitish = main`; this is **correct and matches `v18.9.6`** — `targetCommitish` is only consulted when the tag does not already exist, and `v18.10.1` was pushed first._
- [x] **5.4** Update `BootstrapSdkVersion` in [`eng/Versions.props`](https://github.com/dotnet/msbuild/blob/main/eng/Versions.props) if a fresh SDK was released. Check https://dotnet.microsoft.com/download/visual-studio-sdks — always verify the details for the targeted .NET version.
- [x] **5.4b** Update `tools.dotnet` in [`global.json`](https://github.com/dotnet/msbuild/blob/main/global.json) to the latest released SDK in the targeted band. \
_✅ **No change needed** (verified 2026-09-08): `origin/main` already pins `BootstrapSdkVersion`, `sdk.version` and `tools.dotnet` to **`11.0.100-rc.1.26420.103`**, which is **ahead of** the latest publicly released .NET 11 SDK, **`11.0.100-preview.7.26381.103`** (released 2026-08-11, still `support-phase: preview` — per the authoritative [`releases-index.json`](https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json), not the download page). `rc.1` build `26420` > `preview.7` build `26381`, so applying the "latest released" value would be a **downgrade**. These pins are kept current by VMR flow._ \
_All three pins were already mutually consistent, so 5.4b required nothing beyond 5.4. Same outcome as the 18.9 release — this step is a no-op whenever `main` is tracking a prerelease SDK band ahead of public GA, which is the normal state mid-cycle._
- [x] **5.5** Verify the overall subscription map across **every still-supported branch** — each `vsXX.Y` branch has an Arcade subscription matching its targeted .NET band, and each supported branch's outbound subscriptions land in the right downstream (e.g. SDK band, VMR). \
You can find more info [here](https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/52573/MSBuild-Maestro-Flow). \
_✅ **Verified 2026-09-09** via `darc get-subscriptions` and `darc get-default-channels` — 15 inbound subscriptions and 25 default channels reviewed across every live branch._ \
_**Healthy.** Every live `vsXX.Y` branch has an Arcade subscription matching its targeted .NET band. **No** inbound subscription targets a deleted branch — the Phase 2.3e cleanup is doing its job. All four outbound subscriptions into the VMR report `Current` with no stuck codeflow PRs: `release/10.0.1xx` ← VS 18.0, `release/10.0.4xx` ← VS 18.9, and `main` + `release/11.0.1xx` ← VS 18.12. `18.10`'s own configuration is entirely correct._ \
_(VMR → msbuild backflow reports as "stale" in tooling, but its recorded outcome is `NoUpdate — no source code updates detected`: backflow only fires when the VMR itself edits msbuild sources, and forward flow keeps them identical. Working as designed, not a defect.)_ \
_🔧 **Gap found and fixed — the `dotnet/fsharp` flow.** The `VS 18.11` channel had **no outbound subscription at all**, so `vs18.11` was flowing nowhere — even though `dotnet/fsharp` had already created its `release/dev18.11` branch. This is **structural, not a one-off**: this checklist mentions `fsharp` only in **2.1** and **2.3c**, and both merely **retarget** the pre-existing `fsharp @ main` subscription forward. **No step ever creates** the `VS 18.X → fsharp release/dev18.X` subscription for a newly-branched release, unlike the VMR, which has an explicit creation step (**2.3g**). The `dev18.9` and `dev18.10` subscriptions were both added retroactively — two releases backfilled in a single [maestro PR 64464](https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/64464), which merged **36 minutes before `vs18.11` was branched**. So 18.11 was not missed by bad luck; every release is missed the same way._ \
_Fixed by adding the missing subscription, mirroring the `dev18.10` one exactly: `msbuild (VS 18.11) ==> dotnet/fsharp (release/dev18.11)`, id `90816361-8a2f-4edf-96a2-3083ef4acb81`, via [maestro PR 65083](https://dev.azure.com/dnceng/internal/_git/maestro-configuration/pullrequest/65083) (merged; confirmed live and `Enabled: True` via darc)._ \
_👥 **Ownership — action for 5.6.** This flow is in practice **co-owned by the MSBuild and F# teams**, and historically the F# side has patched it up reactively after noticing it missing. The release that creates `vsXX.Y` is the natural moment to create it, so **MSBuild should own creating it as part of the release process**. Tracked into **5.6**: add a checklist step mirroring 2.3g that creates the `VS {{THIS_RELEASE_VERSION}} → dotnet/fsharp release/dev{{THIS_RELEASE_VERSION}}` subscription when the release branch is created._ \
_Note: the msbuild→fsharp subscriptions carry **no merge policy**, so their PRs need a human merge on the F# side (e.g. [fsharp#20403](https://github.com/dotnet/fsharp/pull/20403) for `dev18.10`, [fsharp#20473](https://github.com/dotnet/fsharp/pull/20473) for `dev18.9`). That is the F# team's call — what matters on our side is that the subscriptions **fire and produce recent PRs**, and they do._ \
_Not fixed — no impact on MSBuild flow today, left as pre-existing cleanup: a stale `VS 16.7 → dotnet/toolset` subscription whose **both** endpoints now 404 (so it cannot fire), three disabled subscriptions, and 14 orphaned default channels pointing at branches that no longer exist._
- [ ] **5.6** Review this tracking issue for any process deviations. If the process changed, create a PR to update `documentation/release-checklist.md` with the improvements.

---

## If 18.11 is a new major version

- [ ] Update VS major version references per [release.md](https://github.com/dotnet/msbuild/blob/main/documentation/release.md):
- [`src/Shared/BuildEnvironmentHelper.cs`](https://github.com/dotnet/msbuild/blob/main/src/Shared/BuildEnvironmentHelper.cs)
- [`src/Shared/Constants.cs`](https://github.com/dotnet/msbuild/blob/main/src/Shared/Constants.cs)
- [`src/Framework/Telemetry/TelemetryConstants.cs`](https://github.com/dotnet/msbuild/blob/main/src/Framework/Telemetry/TelemetryConstants.cs)

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.