Skip to content

Add migrate-dotnet8-to-dotnet9 skill#182

Open
danmoseley wants to merge 29 commits intodotnet:mainfrom
danmoseley:migrate-dotnet8-to-dotnet9
Open

Add migrate-dotnet8-to-dotnet9 skill#182
danmoseley wants to merge 29 commits intodotnet:mainfrom
danmoseley:migrate-dotnet8-to-dotnet9

Conversation

@danmoseley
Copy link
Member

@danmoseley danmoseley commented Mar 4, 2026

Recreated from #154 (original PR was orphaned when the fork relationship with dotnet/skills was re-established).

Add .NET 8 → .NET 9 Migration Skill

Adds a new skill to guide agents through migrating .NET 8 projects to .NET 9, systematically resolving all breaking changes. Modeled after the migrate-dotnet9-to-dotnet10 skill structure.

Structure

  • SKILL.md — 6-step migration workflow (TFM update → build → source fixes → behavioral fixes → infrastructure → verify)
  • 10 reference documents covering all .NET 9 breaking change categories:
    • csharp-compiler.md — C# 13 compiler changes (InlineArray on records, iterator safe context, collection expressions)
    • core-libraries.md — params span overloads, SYSLIB0054–0057, BigInteger limits, String.Trim removal
    • sdk-msbuild.md — Terminal Logger, VS version requirements, out-of-support TFM warnings
    • aspnet-core.md — ValidateOnBuild/ValidateScopes, ForwardedHeaders, middleware changes
    • efcore.md — Pending migrations exception, explicit transactions, Cosmos DB discriminator/ rename, sync I/O
    • cryptography.md — X509CertificateLoader migration (SYSLIB0057)
    • serialization-networking.md — BinaryFormatter removal, JsonDocument null, HttpClientFactory SocketsHttpHandler
    • winforms-wpf.md — WFO1000 security analyzers, PictureBox HttpRequestException, StatusStrip
    • containers-interop.md — zlib removal from images, CET support, Monitor tag changes
    • deployment-runtime.md — Env var precedence reversal, floating-point saturation, empty env vars
  • eval.yaml — 12 scenarios with setup files, assertions, and rubrics

Key design decisions

  • BinaryFormatter: Skill instructs agent to stop and ask the user which replacement serializer to use (major architectural decision) rather than picking one
  • Reference delegation: SKILL.md acts as a concise index pointing to reference docs for detailed fix guidance, matching the 9→10 pattern
  • expect_activation: false: This is a pure knowledge/reference skill with no MCP tools. Activation via SkillInvokedEvent is unreliable for this skill type — the skill content influences agent output through SkillDirectories injection but doesn't always trigger explicit invocation events.

Eval Results (claude-opus-4.6, 3 runs, judged by claude-opus-4.6)

Scenario Baseline With Skill Δ Verdict
Empty env vars, ZIP encoding, keyed DI services 2.3/5 3.0/5 +0.7
C# 13 compiler (InlineArray, iterator, collection expr) 3.0/5 3.0/5 0.0
ASP.NET Core (DI validation, forwarded headers, HttpClientFactory) 2.7/5 2.0/5 -0.7
EF Core (migrations, transactions, Cosmos discriminator) 3.0/5 3.0/5 0.0
EF Core Cosmos DB (id format, HasIndex, sync I/O) 3.7/5 4.3/5 +0.6
JsonDocument null, BinaryFormatter, UserAgent nullable 5.0/5 5.0/5 0.0
CI pipeline (Terminal Logger, VS version, net7.0 warnings) 2.3/5 2.0/5 -0.3
WinForms (WFO1000, PictureBox, StatusStrip) 3.3/5 3.3/5 0.0
Containers (zlib removal, env var precedence, Monitor) 3.3/5 4.3/5 +1.0
EF Core Cosmos DB (discriminator, sync I/O, HasIndex) 4.3/5 4.0/5 -0.3
Library (String.Trim, keyed services, InlineArray size) 2.7/5 3.0/5 +0.3
Container env var precedence + zlib 4.0/5 4.7/5 +0.7

Observations:

  • Skill adds measurable value on obscure/niche changes (empty env vars, Cosmos DB id format, zlib removal, ZIP UTF-8 flag, keyed services no-fallback) where Opus doesn't reliably know the answer from training data
  • Skill adds overhead on well-known changes (ASP.NET Core, HttpClientFactory) where Opus's base knowledge is already strong
  • Overfitting score clean at 0.09 (well below 0.25 threshold)
  • Statistical significance requires more runs (3 runs directionally positive but not significant)

Copilot AI review requested due to automatic review settings March 4, 2026 02:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new migrate-dotnet8-to-dotnet9 skill to the plugins/dotnet plugin, enabling AI agents to systematically guide .NET 8 projects through migration to .NET 9. It is modeled after the existing migrate-dotnet9-to-dotnet10 skill structure, providing a 6-step migration workflow backed by 10 technology-area reference documents and 12 eval scenarios.

Changes:

  • Adds the SKILL.md workflow with frontmatter, 6-step migration process, and reference loading table for the new skill
  • Adds 10 reference documents (csharp-compiler.md, core-libraries.md, sdk-msbuild.md, aspnet-core.md, efcore.md, cryptography.md, serialization-networking.md, winforms-wpf.md, containers-interop.md, deployment-runtime.md) covering all .NET 9 breaking change categories
  • Adds eval.yaml with 12 eval scenarios and a CODEOWNERS entry assigning ownership to @danmoseley

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/SKILL.md Core skill workflow with 6 migration steps and reference loading table
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/aspnet-core.md ASP.NET Core 9 breaking changes reference
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/containers-interop.md Container and CET interop breaking changes reference
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/core-libraries.md Core libraries breaking changes including params span overloads and SYSLIB codes
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/cryptography.md Cryptography breaking changes (SYSLIB0057 / X509CertificateLoader migration)
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/csharp-compiler.md C# 13 compiler breaking changes reference
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/deployment-runtime.md Deployment and runtime configuration breaking changes
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/efcore.md EF Core 9 breaking changes including Cosmos DB discriminator rename
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/sdk-msbuild.md SDK and MSBuild behavioral changes including Terminal Logger
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/serialization-networking.md BinaryFormatter removal, JSON and HttpClientFactory changes
plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/winforms-wpf.md WinForms WFO1000 analyzers, PictureBox, StatusStrip, and WPF changes
tests/dotnet/migrate-dotnet8-to-dotnet9/eval.yaml 12 evaluation scenarios covering all major breaking change categories
.github/CODEOWNERS Adds ownership entries for the new skill and test directory

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@danmoseley
Copy link
Member Author

/eval

@danmoseley
Copy link
Member Author

/evaluate

danmoseley and others added 23 commits March 4, 2026 14:18
Mirrors the structure and approach of the existing migrate-dotnet9-to-dotnet10 skill.

Covers all .NET 8→9 breaking changes organized by technology area:
- C# 13 compiler (InlineArray records, iterator safe context, collection expressions)
- Core libraries (params span overloads, BinaryFormatter removal, BigInteger, TimeSpan)
- SDK/MSBuild (Terminal Logger, version requirements, .NET 7/Standard warnings)
- ASP.NET Core (HostBuilder validation, forwarded headers, middleware constructors)
- EF Core (pending migrations, explicit transactions, Cosmos DB changes)
- Cryptography (X509Certificate2 SYSLIB0057, SafeEvpPKeyHandle)
- Serialization/Networking (BinaryFormatter, JsonDocument null, HttpClientFactory)
- WinForms/WPF (security analyzers, StatusStrip, PictureBox, XmlNamespaceMaps)
- Containers/Interop (zlib removal, CET support)
- Deployment/Runtime (env var precedence, FP-to-int saturation, MonoVM deprecated)

12 eval scenarios covering all technology areas with assertions and rubrics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BinaryFormatter migration is a major architectural decision affecting data
format, backward compatibility, and stored data migration. The skill should
present options and let the user decide rather than picking a serializer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep SKILL.md as concise index; full guidance lives in
serialization-networking.md reference.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tter duplicate

- Remove BinaryFormatter duplicate from Step 4 (already covered in Step 3)
- Delegate inline fix details for EF Core, FP saturation, HttpClientFactory to references
- Collapse niche SYSLIB0055/0056 into one-liner pointing to reference
- Shorten params span and String.Trim entries

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tivation

- Add setup files (csproj, .cs, Dockerfile, etc.) to 10/12 scenarios so agent
  works with real code instead of pure knowledge questions
- Make prompts task-oriented ('Migrate this app to .NET 9') instead of
  knowledge-oriented ('What breaks?')
- Set expect_activation: false on all scenarios — this is a pure knowledge/
  reference skill with no MCP tools, so activation via SkillInvokedEvent is
  unreliable
- Increase timeouts to 420s to avoid penalizing skilled runs that do more work

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace easy scenarios (BinaryFormatter/X509) where Opus always ties with
harder ones targeting areas where the skill adds measurable value:
- Empty env var behavior on Unix (null vs empty string)
- ZipArchiveEntry UTF-8 bit flag mechanism
- FromKeyedServices no-fallback behavior
- Cosmos DB id format change (discriminator no longer embedded)
- Env var precedence reversal with concrete GC/ThreadPool impact

Keep scenarios where skill consistently helps: Cosmos DB id format,
container zlib removal, CI pipeline net7.0 warnings, WinForms PictureBox.

Increase timeouts to 420s to avoid penalizing skilled runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tell the agent about the migrate-dotnet9-to-dotnet10 skill so it knows
to continue the chain after completing the 8-to-9 migration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The eval scenarios claim to test a .NET 8 to .NET 9 migration but the
starting csproj files already reference EF Core 9.0.0 packages. This
reduces test signal since the package upgrade step is already done.
Change all EF Core package versions to 8.0.0 in scenarios 4, 5, and 10.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The rubric expects the model to warn about the InlineArray 1 MiB
runtime size limit, but the example was only 8 KiB (InlineArray(1024)
with byte element). Increase to 1,048,577 bytes so the breaking change
is concretely applicable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The eval used a non-existent overload of ConfigurePrimaryHttpMessageHandler
with a (handler, _) => {} lambda that doesn't return a handler. Replace
with ConfigureHttpMessageHandlerBuilder which correctly casts
PrimaryHandler to HttpClientHandler -- the pattern that actually breaks
when the default switches to SocketsHttpHandler in .NET 9.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The breaking change example used a non-existent overload of
ConfigurePrimaryHttpMessageHandler. Replace with
ConfigureHttpMessageHandlerBuilder which is the actual API that casts
PrimaryHandler and would break when the default changes to
SocketsHttpHandler. Also fix Option 2 to use the same correct API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clearing KnownProxies and KnownNetworks accepts forwarded headers from
any source, enabling attackers to spoof X-Forwarded-For/Proto/Host
headers. Replace with a warning against this pattern and steer toward
registering only trusted proxy addresses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Disabling header redaction globally with RedactLoggedHeaders(_ => false)
logs Authorization tokens, cookies, and other credentials in cleartext.
Replace with guidance to whitelist only specific non-sensitive headers
and add an explicit warning against disabling redaction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the unsupported NuGet package option with a clear security
warning that BinaryFormatter deserialization enables remote code
execution and should not be re-enabled.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WFO1000 guards against insecure deserialization of WinForms control
properties. Replace the suppression option with a warning explaining
the security risk and recommend the attribute-based fix instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Warn that disabling CET removes hardware-enforced control-flow
integrity (protection against ROP/JOP exploits) and should only
be done when a specific native library is confirmed incompatible.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add context that suppressing PendingModelChangesWarning risks silent
schema drift and that MigrationsUserTransactionWarning suppression
has transaction safety implications. Both are temporary workarounds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark behavioral changes that can cause runtime exceptions with a
warning indicator and move them to the top of the list so agents
address them first. Add rollForward policy guidance to the
global.json update step to prevent SDK resolution issues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e blocks

The combined snippet used obj before declaration, which was misleading.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously listed only SYSLIB0054 and SYSLIB0057, omitting 0055 and 0056.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Setup code uses synchronous Migrate(), not MigrateAsync().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Duplicate 'var cert' declarations would cause compilation errors if pasted as-is.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The claim is confusing in an 8-to-9 migration guide context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previous wording was logically inconsistent about the direction of the type change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danmoseley danmoseley force-pushed the migrate-dotnet8-to-dotnet9 branch from 13bdf96 to bb6208e Compare March 4, 2026 21:19
@ViktorHofer
Copy link
Member

/evaluate

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Skill Validation Results

Skill Scenario Baseline With Skill Δ Skills Loaded Overfit Verdict
migrate-dotnet8-to-dotnet9 App with empty environment variables, ZIP encoding, and keyed DI services 2.7/5 2.3/5 -0.4 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 C# 13 compiler breaking changes — InlineArray on record, iterator safe context, collection expressions 3.0/5 3.0/5 0.0 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 ASP.NET Core app with DI validation, forwarded headers, and HttpClientFactory casting 2.0/5 2.0/5 0.0 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 EF Core app with migration patterns and Cosmos DB discriminator 3.0/5 3.0/5 0.0 ✅ tools: bash ✅ 0.07
migrate-dotnet8-to-dotnet9 EF Core Cosmos DB app with existing documents and composite id format 4.0/5 4.3/5 +0.3 ✅ optimizing-ef-core-queries; tools: bash, skill ✅ 0.07
migrate-dotnet8-to-dotnet9 App with JsonDocument null deserialization and BinaryFormatter fallback 4.3/5 5.0/5 +0.7 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 CI pipeline with Terminal Logger parsing and version constraints 2.3/5 2.7/5 +0.4 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 WinForms app with custom UserControls and PictureBox URL loading 2.0/5 2.7/5 +0.7 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 Containerized app with zlib dependency and runtime configuration 4.7/5 4.7/5 0.0 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 EF Core Cosmos DB app with discriminator and sync I/O 3.7/5 3.3/5 -0.4 ✅ optimizing-ef-core-queries; tools: skill ✅ 0.07
migrate-dotnet8-to-dotnet9 Library with String.Trim span overload, keyed services, and InlineArray 3.7/5 3.3/5 -0.4 ℹ️ not activated (expected) ✅ 0.07
migrate-dotnet8-to-dotnet9 Containerized app with env var precedence reversal and zlib removal 5.0/5 4.3/5 -0.7 ℹ️ not activated (expected) ✅ 0.07

Model: claude-opus-4.6 | Judge: claude-opus-4.6

Full results

Copy link
Member

@AndriySvyryd AndriySvyryd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed EF Core content

@danmoseley
Copy link
Member Author

thanks @AndriySvyryd

danmoseley and others added 5 commits March 4, 2026 18:23
…core.md

Co-authored-by: Andriy Svyryd <AndriySvyryd@users.noreply.github.com>
The code example incorrectly used ReadOnlySpan<char> as a static field,
which is impossible (ref struct). The eval scenario used a local
ReadOnlySpan<char> variable which actually compiles fine on .NET 9 GA,
so it didn't demonstrate the breaking change. Updated both to correctly
show the RC2-to-GA overload removal issue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…llisions

Other version migration skills (e.g., 9-to-10) have similarly named
reference files. Adding the suffix disambiguates them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ViktorHofer
Copy link
Member

/evaluate

@@ -0,0 +1,240 @@
---
name: migrate-dotnet8-to-dotnet9
description: >
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A skills description is loaded into the context every single time, for any user of the plugin. It is the trigger which decides if rest of the skills .md should be loaded or not.

I would suggest to:

  1. Have the description as vanilla "Migrate from net8 to net9"
  2. Have the remarks and all the "DO NOT USE FOR" in the skills body. That means the context cost will be only paid if someone is doing a migration.
  3. Choose between providing a link to published document about breaking changes vs. restating what the document says. If we do both, a web search enabled agent will read the web page as well as the .md here

Copy link
Member Author

@danmoseley danmoseley Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @T-Gro . I did experiment with variations, including what you said (short description and do not use in the body). I ended up with it in the description because in evaluation the skill was not getting loaded in obviously relevant scenarios.

For your 2, that's a good point, but also the skill would be loaded in unecessary situations when the user IS doing a migration. That may be the right trade off of course.

For your 3, also a good point. I think we're learning here. Most skills have chosen to not refer to docs for "core functionality" but maybe they should?

I'm inclined to leave things as is, as we're learning here and it's easy to change later. We presumably don't have many users yet, and hopefully they'll give us feedback in this repo and we can adjust. By the way these things are distributed, we ship almost continuously

BTW perhaps grouping into a migration agent might help the LLM find and load the right skill, without so much tuning of description?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants