name: dpg-migration description: Migration logic for Azure SDK for .NET data-plane libraries migrating from AutoRest/Swagger to TypeSpec-based generation. Uses MCP tools from the generator-agent server for automated deterministic fixes.
DPG Migration Workflow
Data-plane migration workflow for Azure SDK for .NET libraries.
The workflow uses MCP tools from the generator-agent server to automate all deterministic, rule-based fixes (field renames, missing usings, type pattern replacements, nullable annotations, etc.) so the LLM only reasons about non-deterministic errors.
When Invoked
Trigger phrases: "migrate service X", "data-plane migration", "dpg migration", "migrate to TypeSpec", "swagger to TypeSpec migration", "upgrade generator", "generator migration help", "migrate with MCP tools", "use generator-agent tools", "tool-assisted migration", "MCP migration", "automated build fix".
Prerequisites
This skill requires the SDK repository and the spec repo:
| Path | Purpose | Required? |
|---|---|---|
Current repository (azure-sdk-for-net) |
Azure SDK for .NET mono-repo. Data-plane SDK packages typically live under sdk/<service>/<package>/. |
Always |
Sibling spec folder (../azure-rest-api-specs) |
Full or sparse-checkout of the Azure REST API Specs repo. Used for spec-side changes (client.tsp, tspconfig.yaml edits) and as the local spec path for code generation after any spec modification. |
Always |
Sibling TypeSpec folder (../typespec) |
Clone of the microsoft/typespec repo. | Only for diagnosing generator bugs |
The local spec repo path (localSpecsPath) is always required — ask the user for it at the start if not found at ../azure-rest-api-specs. Code generation starts in remote mode (using the commit pinned in tsp-location.yaml) and switches to local mode (/p:LocalSpecRepo=<path>) only after client.tsp or tspconfig.yaml has been modified locally. Once in local mode, all subsequent generation calls use the local spec path — see Generation Mode for details.
MCP Server (Generator Agent)
The MCP server provides deterministic fix tools for the build-fix cycle. Configure in .vscode/mcp.json:
{
"servers": {
"generator-agent": {
"command": "dotnet",
"args": ["run", "--project", "sdk/tools/Azure.GeneratorAgent/src/Azure.GeneratorAgent.csproj", "--framework", "net10.0"]
}
}
}
Available MCP Tools:
| Tool | When to Use | What It Does |
|---|---|---|
discover_project |
First step of any migration — call before all other tools | Returns comprehensive project context: detected plane, package name, service name, emitter config, tsp-location.yaml fields, custom code folder, API surface files, and specsRepoPath (auto-detected at ../azure-rest-api-specs sibling of SDK repo root, null if not found — ask the user for the path when null). |
build_and_classify |
First step of every build-fix iteration | Runs dotnet build, parses output, classifies each error as deterministic or requires-reasoning |
batch_fix |
After build_and_classify returns deterministic errors |
Applies multiple deterministic fixes in one call |
regex_replacement |
Field renames, type pattern replacements | Regex find/replace in a file |
add_using_directive |
CS0246/CS0103 for a known type (47 type→namespace mappings) | Adds a missing using directive |
remove_using_directive |
CS0246 for *.Rest.* or Autorest.* namespaces |
Removes using directives matching a pattern |
nullable_annotation_fix |
CS8625 or CS8600 errors | Adds ? nullable annotation on the error line |
rename_codegen_type |
CS0246 for mismatched *ModelFactory or *ClientBuilderExtensions |
Updates [CodeGenType] attribute to match generated type |
fetch_to_fromlro |
CS0103/CS1061 for Fetch method calls in custom LRO code |
Replaces Fetch(response) with Model.FromLroResponse(response) |
add_codegen_suppress |
CS0111 (duplicate member definition from custom + generated clash) | Adds [CodeGenSuppress] attribute to custom partial class, scanning Generated/ for member signature |
classify_errors |
Re-classify errors after partial fixes | Classifies errors against the deterministic fix registry |
validate_tsp_config |
Before code generation (if not already validated by commit_iteration) |
Validates tspconfig.yaml emitter section: checks @azure-typespec/http-client-csharp key exists, required fields present (emitter-output-dir, namespace, model-namespace), and no invalid properties. Works on file path or string content. |
commit_iteration |
At migration start and whenever code generation fails with a spec-level error. | Finds a valid spec commit remotely. Supports strict (commitOverride), auto-resolve (iterates forward), and fallback (fixes tspconfig.yaml locally when localSpecsPath is provided — no git commit needed). Returns useLocalSpecs=true when tspconfig was fixed locally. See Phase 2 for details. |
pregen_cleanup |
Before first code generation | Removes IncludeAutorestDependency from .csproj |
snapshot_generated |
After code generation, before build-fix cycle | Takes SHA-256 snapshot of all Generated/ files. Enables auto-verification in build_and_classify. |
verify_generated_unchanged |
After build-fix cycle completes | Compares Generated/ files against snapshot. Reports violations (modified/deleted/added). Set autoRevert=true to auto-restore via git checkout. |
migrate_test_samples |
After src builds, before test build | Moves Generated/Samples/ to Samples/ |
finalize_migration |
After ALL builds succeed | Runs Export-API.ps1 and Update-Snippets.ps1 |
run_tests |
After build succeeds, to verify tests pass | Runs dotnet test --no-build with configurable filter (defaults to excluding live tests) |
Inputs
The discover_project tool auto-resolves most variables below — it is called in Phase 0 as the first step of any migration. This skill applies only when Plane = dpg.
For the purposes of diagnosing generator bugs, the data-plane emitter is located in this repo under eng/packages/http-client-csharp/. The base emitter is in the microsoft/typespec repo under packages/http-client-csharp/.
| Variable | Example | discover_project field |
Description |
|---|---|---|---|
LIBRARY_PATH |
sdk/communication/Azure.Communication.Messages |
LibraryPath |
Relative path to the SDK package directory |
PACKAGE_NAME |
Azure.Communication.Messages |
PackageName |
Full NuGet package / directory name |
SERVICE_NAME |
communication |
ServiceName |
The folder name immediately after sdk/ |
EMITTER_PACKAGE_JSON_PATH |
see below | EmitterPackageJsonPath |
Path to the emitter package.json |
| Plane | dpg |
Plane |
Should resolve to dpg for this skill |
SPECS_REPO_PATH |
../azure-rest-api-specs |
SpecsRepoPath |
Absolute path to local spec clone (null if not found — ask user) |
| Plane | EMITTER_PACKAGE_JSON_PATH |
Target emitter in tspconfig.yaml |
|---|---|---|
| DPG | eng/azure-typespec-http-client-csharp-emitter-package.json |
azure-typespec/http-client-csharp |
The following variables come from tsp-location.yaml (returned in discover_project's TspLocationFields when the file exists):
| Variable | Example | TspLocationFields key |
Description |
|---|---|---|---|
SPEC_DIR |
specification/<service>/<data-plane-spec-dir> |
directory |
TypeSpec directory in azure-rest-api-specs |
SPEC_COMMIT |
37052dfa3f... |
commit |
Commit SHA from the spec repo |
The following variable is not auto-resolved by discover_project — determine it from the spec:
| Variable | Example | Description |
|---|---|---|
API_VERSION |
2025-01-01 |
Target API version from the spec |
Migration Status File
Migrations often span multiple sessions and machines. To enable seamless pickup, a migration-status.md file is stored in the SDK package directory and committed to the migration branch.
Location
{LIBRARY_PATH}/migration-status.md
Example: sdk/communication/Azure.Communication.Messages/migration-status.md
Template
# Migration Status — {PACKAGE_NAME}
**Tracking Issue:** [#{ISSUE_NUMBER}](https://github.com/Azure/azure-sdk-for-net/issues/{ISSUE_NUMBER})
**Last Updated:** {DATE}
## PRs
| PR | URL | Status |
|----|-----|--------|
| **Spec** | {URL or "Not created"} | {Draft/Open/Merged} |
| **SDK** | {URL or "Not created"} | {Draft/Open/Merged} |
| **Generator** | {URL or "N/A"} | {Draft/Open/Merged/N/A} |
## Branches
| Repo | Branch | Fork Remote |
|------|--------|-------------|
| azure-sdk-for-net | `{branch}` | `{remote}` |
| azure-rest-api-specs | `{branch}` | `{remote}` |
## Phase Tracker
**Status legend:** ✅ Done | 🔄 In Progress | ❌ Blocked | ⏭️ Not Started
| Phase | Status | Notes |
|-------|--------|-------|
| Phase 0 — Sync & Resume | {status} | |
| Phase 1 — Discovery & Planning | {status} | |
| Phase 2 — Create/Update tsp-location.yaml | {status} | |
| Phase 3 — Handle Legacy Configuration | {status} | |
| Phase 4 — Update Custom Code | {status} | |
| Phase 5 — Code Generation | {status} | |
| Phase 6 — Build-Fix Cycle | {status} | |
| Phase 7 — Changelog | {status} | |
| Phase 8 — Test Project Build | {status} | |
| Phase 9 — Test Execution | {status} | |
| Phase 10 — Finalization | {status} | |
| Phase 11 — Create Pull Requests | {status} | |
| Phase 12 — Verify and Summarize | {status} | |
## ApiCompat Baseline Summary
| Error Type | Count | Action |
|-----------|-------|--------|
| MembersMustExist | {N} | Fix with custom code |
| TypesMustExist | {N} | Fix with @@clientName |
| CannotChangeAttribute | {N} | Acceptable to baseline |
| ... | | |
## Known Issues
- {issue description and link}
## Next Steps
1. {next action}
When to Save
Commit and push migration-status.md to the migration branch at these points:
- After Phase 0 — create the initial
migration-status.md(or update if resuming) - After any phase completes — update phase status
- Before ending a session — always save current progress
- After creating PRs — update PR links
Save Command
git add {LIBRARY_PATH}/migration-status.md
git commit -m "Update migration status for {PACKAGE_NAME}"
git push
When to Delete
Remove migration-status.md from the branch when the migration is complete (Phase 12). It should not be included in the final PR merge to main.
Phase 0 — Sync & Resume
Before any migration work:
Discover Project
Call discover_project with the absolute path to the SDK package directory. This returns the detected plane, package name, service name, emitter config, tsp-location.yaml fields, custom code folder, API surface files, and SpecsRepoPath — all in one call. If SpecsRepoPath is null, ask the user for the path to their local azure-rest-api-specs clone.
Resume Check
- Check if
{LIBRARY_PATH}/migration-status.mdexists on the current branch.- If it exists: Read and parse the status file. Report the current phase to the user and resume from the first incomplete phase.
- If it does not exist: This is a fresh migration — proceed with sync and Phase 1.
Sync Repositories
Merge the latest main branch into all repos.
Save Status
Create migration-status.md with all phases marked ⏭️ (or update existing with Phase 0 marked ✅). Commit and push to the migration branch.
Phase 1 — Discovery & Planning
Use explore agents in parallel:
- Find the spec: Search
../azure-rest-api-specs/specification/<service>/formain.tsp/tspconfig.yaml. Determine TypeSpec vs Swagger. - Find the existing SDK: Check for
tsp-location.yaml(already migrated, returned indiscover_project'sHasTspLocation) orsrc/autorest.md(legacy Swagger, returned inHasAutorestMd). - Inventory existing csharp customizations in spec: Search
.tspfiles for@clientName("...", "csharp")and@@clientNamedecorators. Check forback-compatible.tsp. - Snapshot old API surface: Read
api/<PACKAGE_NAME>.net*.cs(listed indiscover_project'sApiSurfaceFiles) — extract all public type names for later rename resolution. - Extract autorest rename mappings: From
src/autorest.md, extractrename-mappingandprepend-rp-prefixentries. - Identify custom code folder convention:
Custom/,Customization/, orCustomized/(returned indiscover_project'sCustomCodeFolder).
If tsp-location.yaml exists with a commit and directory: Proceed directly to Phase 2 — the commit_iteration tool will validate the spec remotely. Do NOT check the local spec repo or read local tspconfig.yaml.
If no tsp-location.yaml exists (fresh migration): Search ../azure-rest-api-specs/specification/<service>/ for main.tsp / tspconfig.yaml to determine the spec directory. Also inventory existing csharp customizations in spec (search .tsp files for @clientName("...", "csharp") and @@clientName decorators, check for back-compatible.tsp).
Present a summary plan and ask the user to confirm.
After confirmation, update migration-status.md to mark Phase 1 as ✅ and save.
Phase 2 — Create/Update tsp-location.yaml
Goal: Point the library at the correct spec and emitter.
- Check if
{LIBRARY_PATH}/tsp-location.yamlexists.- If it exists (already TypeSpec-based): Read and parse
repo,directory, andcommitfields. Skip to step 3. - If it does not exist (migrating from swagger): Create
tsp-location.yamlwith the following fields:
Find the correctdirectory: specification/<service>/<data-plane-spec-dir> commit: <latest commit SHA from azure-rest-api-specs main> repo: Azure/azure-rest-api-specs cleanup: true emitterPackageJsonPath: <value from discover_project's EmitterPackageJsonPath>directoryby searching../azure-rest-api-specs/specification/<service>/for a TypeSpec project (main.tsp+tspconfig.yaml) that targets the same service.
- If it exists (already TypeSpec-based): Read and parse
- Always verify
emitterPackageJsonPathis set to the correct plane-specific value (fromdiscover_project'sEmitterPackageJsonPath). Update it if missing or incorrect — this field is required for code generation to use the right emitter. - Resolve the correct commit SHA (see below).
- Update the
commitfield with the resolved SHA. - If the
directorypath no longer exists, search for TypeSpec projects with similar service names. - Preserve all other existing fields.
Commit SHA Resolution
Call commit_iteration to resolve the correct spec commit. The tool handles all validation and iteration internally.
- User provides a commit SHA: Pass
commitOverride. The tool validates strictly — if invalid, tell the user why. Do NOT iterate or fall back. - No commit provided: Pass
sdkProjectPath,tspLocationPath,specsRelativeDirectory, andlocalSpecsPath. The tool auto-resolves by validating the current commit and iterating forward if needed. - All remote candidates invalid: The tool fixes
tspconfig.yamllocally and returnsuseLocalSpecs=true. Setgen_modetolocalin session state — Phase 5 uses this to decide how to run code generation.
Additional tsp-location.yaml fields:
directorymust point to the folder containingmain.tspandtspconfig.yaml.- Optional:
additionalDirectoriesarray for shared TypeSpec libraries.
Phase 3 — Handle Legacy Configuration
Remove AutoRest dependency
- Call
pregen_cleanupwith the project path — removes<IncludeAutorestDependency>true</IncludeAutorestDependency>from.csprojfiles.
Handle legacy AutoRest configuration
If src/autorest.md exists:
- Extract any still-relevant config for reference:
namespace,title,requireURL,output-folder, and rename directives. - Preserve only the information that still matters to the TypeSpec-based package shape.
- Delete
autorest.mdafter the TypeSpec generation path is working — git history preserves it. - Do NOT create a
client.tspin the SDK repo. The TypeSpec source lives in the spec repo.
Phase 4 — Update Custom Code
CodeGen namespace and attributes
In all non-Generated .cs files under {LIBRARY_PATH}/src/:
add_using_directive— Addusing Microsoft.TypeSpec.Generator.Customizations;where[CodeGenattributes are used.regex_replacement— ReplaceCodeGenClient→CodeGenTyperegex_replacement— ReplaceCodeGenModel→CodeGenType
Data-plane code transformations
These are also handled by MCP tools during build-fix, but can be applied before code generation:
regex_replacement—_pipeline→Pipeline(field → property)remove_using_directive— Removeusing AutoRest.CSharp.Core;regex_replacement—_serializedAdditionalRawData→_additionalBinaryDataPropertiesregex_replacement—serializedAdditionalRawData→additionalBinaryDataPropertiesregex_replacement—FromCancellationToken(cancellationToken)→cancellationToken.ToRequestContext()
Phase 5 — Code Generation
Goal: Regenerate code with the new TypeSpec emitter.
Generation Mode
Code generation operates in one of two modes:
SELECT value FROM session_state WHERE key = 'gen_mode';
| Mode | When | Command |
|---|---|---|
| Remote (default) | Commit from tsp-location.yaml resolved successfully, no local spec edits made |
dotnet build /t:GenerateCode |
| Local | After ANY local modification to client.tsp or tspconfig.yaml, or when commit_iteration returned useLocalSpecs=true |
dotnet build /t:GenerateCode /p:LocalSpecRepo=<localSpecsPath> |
Transition rules:
- Remote → Local: Triggered by the first local edit to
client.tsportspconfig.yaml, or whencommit_iterationreturnsuseLocalSpecs=true. This is a one-way transition. - Local → Remote: Only in Phase 11, for the final CI-reproducibility verification after spec changes are pushed and a real commit SHA is obtained.
-- Set at migration start (Phase 0):
INSERT OR REPLACE INTO session_state (key, value) VALUES ('gen_mode', 'remote');
-- After first spec edit or useLocalSpecs=true:
UPDATE session_state SET value = 'local' WHERE key = 'gen_mode';
Running Code Generation
Pass localSpecsPath only when in local mode:
- Remote mode:
dotnet build /t:GenerateCode - Local mode:
dotnet build /t:GenerateCode /p:LocalSpecRepo=<localSpecsPath>
Always use dotnet build /t:GenerateCode. Do NOT use tsp-client update.
Code Generation Failure
If generation fails, diagnose the cause:
Customization file issue: Check if a customization file is causing the failure. Temporarily remove or fix it, then re-run. If it still fails with no customizations → generator bug.
Spec-level error (e.g.,
invalid-schema,duplicate-example-file, or any TypeSpec compiler error that is not a C# build error):- Re-call
commit_iteration(nocommitOverride, always passlocalSpecsPath) — it finds the next valid commit. - If found → retry code generation.
- If every commit fails →
commit_iterationfixestspconfig.yamllocally and returnsuseLocalSpecs=true. Switch to local mode and use/p:LocalSpecRepo=<localSpecsPath>for all subsequent generation.
- Re-call
After generation:
Post-Generation Checklist
- Check
src/Generated/for output files — verify file contents changed, not just file names. - Use
git diff --statto confirm the scope of changes. A typical migration touches hundreds of files with significant content changes. - MANDATORY: Call
snapshot_generatedwith the project path to lock down the Generated/ directory. This enables auto-verification in every subsequentbuild_and_classifycall — any accidental modification to Generated/ files will be detected and reverted automatically.
Build errors, ApiCompat, tests, and API export are handled in their dedicated phases (Phase 6, 8, 9, 10).
Phase 6 — Build-Fix Cycle
Goal: Get the library to compile successfully through iterative error fixing.
You (the LLM) are the orchestrator — call MCP tools for deterministic fixes, reason about non-deterministic errors using the classification trees below.
Command Macros
| Macro | Command |
|---|---|
[GENERATE] |
dotnet build /t:GenerateCode (remote mode) or dotnet build /t:GenerateCode /p:LocalSpecRepo=<localSpecsPath> (local mode — see Phase 5 Generation Mode) |
[TEST] |
Call run_tests MCP tool (or dotnet test --no-build --filter "TestCategory!=Live" 2>&1 | Select-Object -Last 30) |
Execution Protocol
LOOP:
1. Call `build_and_classify` → builds and classifies ALL errors
2. IF zero errors → EXIT LOOP
3. IF error count is not decreasing after 3 iterations → escalate to user
4. Separate errors into deterministic and requires-reasoning buckets
5. Apply ALL deterministic fixes via `batch_fix`
6. For requires-reasoning errors, classify and apply fixes in batch
7. Regenerate based on what changed:
- Spec changes (client.tsp edits) → [GENERATE]
- Generator attribute changes → [GENERATE]
- Generator code changes → rebuild the data-plane emitter, then [GENERATE]
- Customization-only changes → no regeneration needed
- Stale custom file (e.g., custom `FooResource.cs` exists but
`Generated/FooResource.cs` was renamed to `BarResource.cs` or removed) →
rename custom file to match the new generated name, or remove it if
the generated type no longer exists. Then [GENERATE].
- Error in Generated/ file with custom class present →
remove custom file, then [GENERATE] to isolate cause.
If error gone → custom file was the cause. Re-introduce incrementally.
If error persists with zero custom files → generator bug, STOP and report.
8. GOTO 1
Max 10 iterations. If still failing, escalate to user.
Generated/ Auto-Guard: When a snapshot exists (taken in Phase 5), every
build_and_classifycall automatically verifies that no Generated/ files were modified. If violations are detected, they are auto-reverted viagit checkoutand reported in the build result undergeneratedGuard. Additionally,regex_replacement,add_using_directive,remove_using_directive, andnullable_annotation_fixwill refuse to modify files inside Generated/ directories.
Key decision points for regeneration:
- After editing
[CodeGenType],[CodeGenSuppress],[CodeGenMember], or[CodeGenSerialization]attributes → [GENERATE] before rebuilding - After editing
client.tsp→ switch to local mode (if not already), then [GENERATE] before rebuilding - After removing or renaming a custom file that contained generator attributes → [GENERATE] before rebuilding (the generator will produce different output without those attributes)
- After editing only custom
.cscode (no generator attributes) → no regeneration needed - If build returns 0 errors but previous build had errors → verify no regressions by rebuilding once more
- When in doubt, regenerate. Skipping regeneration after customization changes is a common source of confusing errors.
ApiCompat Error Handling
⛔ NEVER create or modify ApiCompatBaseline.txt to suppress ApiCompat errors. This is a hard rule — no exceptions.
⛔ NEVER remove or modify ApiCompatVersion from the .csproj to suppress these errors. The version must stay to enforce compatibility.
ApiCompat errors surface when dotnet build detects breaking changes vs the previously shipped API. Run dotnet pack --no-restore to get the full list. For each error, create a backward-compat shim in Custom/BackwardCompat/:
| ApiCompat Rule | What It Means | Fix |
|---|---|---|
MembersMustExist (constructor) |
Protected constructor removed from abstract type | Add protected TypeName(params) : base(params) { } in Custom/BackwardCompat/<TypeName>.cs. Never edit Generated/ files. |
MembersMustExist (method/property) |
Method signature changed (e.g., IReadOnlyDictionary → IDictionary) |
Create forwarding overloads in Custom/BackwardCompat/<ClientName>.cs with the old IReadOnlyDictionary parameter type that convert and delegate to the new IDictionary method. Add #pragma warning disable AZC0002 if overloads lack CancellationToken. For async forwarding methods, always use ConfigureAwait(false) on the awaited call. Never edit Generated/ files. |
MembersMustExist (ModelFactory) |
ModelFactory overload signature changed (usually a parameter's enum type was removed) | Add forwarding overload with old signature in Custom/BackwardCompat/<ModelFactoryName>.cs. Accept the removed enum param and discard it, delegating to the new method. Mark [EditorBrowsable(Never)]. If the removed param's type no longer exists, create a stub struct in MissingEnumTypes.cs. |
MembersMustExist (SerializedAdditionalRawData) |
Protected field renamed to _additionalBinaryDataProperties |
Re-declare the old field in Custom/BackwardCompat/<TypeName>.cs: protected internal IDictionary<string, BinaryData> SerializedAdditionalRawData; with #pragma warning disable SA1307 and SA1401. |
MembersMustExist (missing setter) |
Property lost its setter | [CodeGenSuppress("Property")] + re-declare with { get; set; } |
MembersMustExist (missing enum value) |
Enum value removed/renamed | Add [EditorBrowsable(EditorBrowsableState.Never)] deprecated value in custom partial |
MembersMustExist (property type changed) |
Property type changed (e.g., custom enum → string) |
Use [CodeGenSuppress("Property")] to suppress the generated property, then re-declare it in a custom partial class returning the old type. Create a stub type (e.g., a readonly struct with implicit operator from string) in MissingEnumTypes.cs, marked [EditorBrowsable(Never)]. Never edit Generated/ files. |
TypesMustExist |
Type renamed/removed | @@clientName to restore old name, OR create stub type in custom code |
TypesMustExist (extension class renamed) |
DI extension class renamed (e.g., AIFooClientBuilderExtensions → FooClientBuilderExtensions) |
Create a stub public static class with the old name containing extension methods that delegate to the new class. Use global:: namespace qualifiers if the stub is inside Microsoft.Extensions.Azure (bare Azure.X resolves wrong). Copy any [RequiresUnreferencedCode]/[RequiresDynamicCode] attributes from the target method. |
CannotMakeMemberNonVirtual |
virtual → non-virtual (abstract type) |
Add protected constructor shim to keep type inheritable (check for serialization constructor conflicts first — see constructor row above) |
CannotSealType |
Type effectively sealed (private ctor) | Same as above — add protected constructor to keep type inheritable |
File organization: Place backward-compat shims in Custom/BackwardCompat/, with each file named after the type it extends:
<TypeName>.cs— e.g.,PersistentAgentsModelFactory.csfor ModelFactory shims,SomeClient.csfor client method shims,MessageDeltaTextAnnotation.csfor field renamesMissingEnumTypes.cs— stub types for removed enums (these don't map to a single generated type)
Mark all backward-compat shims with:
[EditorBrowsable(EditorBrowsableState.Never)]— hide from IntelliSense#pragma warning disable AZC0002if needed for missing CancellationToken- For async forwarding methods, always
await ... .ConfigureAwait(false)(library best practice) - Code comment explaining why the shim exists
Detailed Examples
PersistentAgentsModelFactory.cs — When a ModelFactory method's old signature had an enum parameter that no longer exists (e.g., MessageDeltaChunkObject), create a forwarding overload that accepts the old enum and discards it:
// Custom/BackwardCompat/PersistentAgentsModelFactory.cs
#nullable disable
using System.ComponentModel;
namespace Azure.AI.Agents.Persistent
{
public static partial class PersistentAgentsModelFactory
{
// backward-compat: old overload had MessageDeltaChunkObject param that no longer exists
[EditorBrowsable(EditorBrowsableState.Never)]
public static MessageDeltaChunk MessageDeltaChunk(
string id,
MessageDeltaChunkObject @object, // old enum param — accepted but discarded
MessageDelta delta)
{
return MessageDeltaChunk(id: id, delta: delta);
}
}
}
MessageDeltaTextAnnotation.cs — When the protected field SerializedAdditionalRawData was renamed to _additionalBinaryDataProperties:
// Custom/BackwardCompat/MessageDeltaTextAnnotation.cs
using System;
using System.Collections.Generic;
namespace Azure.AI.Agents.Persistent
{
public abstract partial class MessageDeltaTextAnnotation
{
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
#pragma warning disable SA1401 // Fields should be private
protected internal IDictionary<string, BinaryData> SerializedAdditionalRawData;
#pragma warning restore SA1401
#pragma warning restore SA1307
}
}
SomeClient.cs async pattern — Always use ConfigureAwait(false) in async forwarding overloads:
#pragma warning disable AZC0002
public partial class SomeClient
{
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual async Task<Response<Model>> CreateAsync(
IReadOnlyDictionary<string, string> metadata,
CancellationToken cancellationToken)
{
return await CreateAsync(
(IDictionary<string, string>)(metadata?.ToDictionary(k => k.Key, k => k.Value)),
cancellationToken).ConfigureAwait(false);
}
}
#pragma warning restore AZC0002
Common Pitfalls
[CodeGenType]attribute mismatch: When a custom partial class uses[CodeGenType("X")], the valueXmust exactly match the generated class name. If the generated name changed during migration, the partial classes silently fail to merge — methods from both sides become invisible, producing dozens of spuriousMembersMustExisterrors. Always verify the generated class name before assuming individual methods are missing.XML
crefreferences break after signature changes: After changing method parameter types (e.g.,IDictionary→IReadOnlyDictionary), XML doc comments in custom files that reference the old signatures viacrefattributes produce CS1574 errors. Scan custom*.csfiles forcrefattributes containing the old type and update them.ModelFactory class name mismatch: The generated ModelFactory class name may differ from what the custom partial class expects. If most ModelFactory methods show as
MembersMustExisterrors, check whether the generated and custom ModelFactory classes have matching names/[CodeGenType]attributes before creating individual shims.
Key Insight: Generator and Customization File Interaction
The generator reads existing customization files ([CodeGenSuppress], [CodeGenType], [CodeGenMember], [CodeGenSerialization]) and produces different output based on them:
- Errors in
Generated/are often caused by stale customization files, not generator bugs. - Fix by editing the customization file, then re-running
[GENERATE]. - If a generated file has errors that trace back to a problematic custom file, remove (or rename/comment-out) the custom file and regenerate. The generator will produce clean output without the stale customization influence. You can then re-introduce only the parts of the custom file that are still needed, one piece at a time, regenerating after each change.
- After ANY change to a custom file that contains generator attributes (
[CodeGenType],[CodeGenSuppress],[CodeGenMember],[CodeGenSerialization]), you MUST regenerate with[GENERATE]before rebuilding. The generator reads these attributes at generation time — building without regenerating will use stale generated code that doesn't reflect your customization changes. - Only after eliminating customization interference can you identify true generator bugs.
Error Classification
Given: error in file F with message M
1. IF F is under `src/Generated/`:
a. Find the corresponding customization file
b. Fix the customization to match new generated code
c. Run [GENERATE] then [BUILD]
d. If error persists after removing all customizations → generator bug
→ STOP: Do NOT suppress. Report to user with full details (see Generator Bug Reporting).
2. IF F is under custom code:
a. Renamed/missing type → update custom code or add rename
b. Internal type → use an accessibility fix in the spec or CodeGenType
3. IF error is structural in Generated/ with correct spec → generator bug
→ STOP: Do NOT suppress. Report to user with full details (see Generator Bug Reporting).
Data-plane error patterns
These patterns are specific to data-plane migrations. Apply them during the shared skill's "Build-Fix Cycle" phase, in addition to the common customization patterns.
GeneratorPageableHelpers → CollectionResult pattern
- If code uses
GeneratorPageableHelpers.CreatePageableor similar, replace it with the corresponding generatedCollectionResulttype from theGenerated/folder. - If no
CollectionResulttype exists inGenerated/:- Look for a
[CodeGenSuppress]attribute that suppresses the internal method which would create theCollectionResult. - Comment out or remove that
[CodeGenSuppress]attribute. - Re-run code generation to produce the
CollectionResulttype. - After regeneration, use the generated type.
- Look for a
- Do NOT create
CollectionResulttypes manually — they must be generated.
ToRequestContent() removal
- Input models now have an implicit cast to
RequestContent. - Replace
foo.ToRequestContent()with justfoo. - Example:
using RequestContent content = details.ToRequestContent();→using RequestContent content = details; - Keep the
usingstatement; only remove the.ToRequestContent()call.
FromCancellationToken replacement
- Replace
RequestContext context = FromCancellationToken(cancellationToken); - With
RequestContext context = cancellationToken.ToRequestContext();
Mismatched ModelFactory type names
- If a custom type ending in
ModelFactoryhas a different name than the generated type ending inModelFactory, update theCodeGenTypeattribute in the custom type to match the generated type name.
Mismatched ClientBuilderExtensions type names
- If a custom type ending in
ClientBuilderExtensionshas a different name than the generated type ending inClientBuilderExtensions, update theCodeGenTypeattribute in the custom type to match the generated type name.
Fetch methods in custom LRO methods
- The old
Fetchmethods are replaced by staticFromLroResponsemethods on the response models. - Update custom LRO methods to use
ResponseModel.FromLroResponse(response)instead of callingFetchmethods. - Do NOT create
Fetchmethods manually — call the generatedFromLroResponsemethod.
FromResponse method removal
FromResponsemethods have been removed from models.- Use explicit cast from
Responseto the model type instead. - Example:
var model = ModelType.FromResponse(response);→var model = (ModelType)response;
Priority Order
- Missing/renamed types (CS0234, CS0246)
- Accessibility issues (CS0051, CS0122)
- Signature mismatches (CS1729, CS0029, CS1503)
- Duplicate definitions (CS0111)
- Other errors — investigate individually
Customization Patterns
- Partial classes — Extend generated types without editing
Generated/. [CodeGenType("SpecName")]— Link custom class to generated counterpart.[CodeGenSuppress("MemberName", typeof(...))]— Suppress a generated member.[CodeGenMember("MemberName")]— Link custom property to generated counterpart.- Match existing custom code folder convention —
Custom/,Customization/, orCustomized/. - Add justification comments — Every custom code file must include a code comment (
//) explaining why the customization exists (e.g., backward-compat shim for removed enum value, property rename from flattening change). This helps future maintainers understand the intent and decide whether the shim can be removed. - No XML
<summary>on partial type declarations in custom code — Partial types in customization files do not need an XML doc summary on the type itself; the generated partial already carries the summary and it will be merged by the compiler. Adding a summary here risks leaking internal justification into the released NuGet package's XML documentation. Only add XML docs on new members (properties, methods) introduced in custom code. - Mark backward-compat shims with
[EditorBrowsable(EditorBrowsableState.Never)]— Deprecated shim members (e.g., restored enum values, renamed properties) should be hidden from IntelliSense to guide users toward the new API surface.
Further reading: C# Customization Guide, TypeSpec client customization.
Phase 7 — Changelog
Do NOT hand-edit metadata.json — it is auto-generated.
CHANGELOG.md
## <VERSION> (Unreleased)
### Features Added
- Migrated from Swagger to TypeSpec-based generation
Phase 8 — Test Project Build
- Call
migrate_test_sampleswith the project path — movestests/Generated/Samples/totests/Samples/. Do NOT run[GENERATE]in tests. - Call
build_and_classifyon the test project path — same build-fix loop as Phase 6, but for test code.- Test files are NOT generated — edit them directly.
- Check
src/Generated/for actual new type names before writing fixes. - Use
batch_fixfor deterministic errors, reason about the rest.
- Max 10 iterations. If still failing, skip to finalization.
Phase 9 — Test Execution
- Call
run_testswith the project path — runsdotnet test --no-build --filter "TestCategory!=Live"and returns structured pass/fail results. - Fix failures, call
build_and_classify, thenrun_testsagain. - Repeat (max 5 iterations). If still failing, continue to finalization.
Phase 10 — Finalization
Call finalize_migration with the project path — runs Export-API.ps1 and Update-Snippets.ps1 automatically.
Or run manually from the repository root:
.\eng\scripts\Export-API.ps1 -ServiceDirectory SERVICE_NAME
.\eng\scripts\Update-Snippets.ps1 -ServiceDirectory SERVICE_NAME
Phase 11 — Create Pull Requests
Once dotnet build passes, create separate PRs for each category:
| Category | Repository | PR needed? |
|---|---|---|
| Spec changes | azure-rest-api-specs |
If any spec files were modified |
| Generator changes | azure-sdk-for-net |
If generator code was fixed |
| SDK migration | azure-sdk-for-net |
Always |
Step 1 — Classify Changes
During the iteration loop, changes fall into three categories. Identify which ones apply by reviewing the modified files across both repositories.
Step 2 — Create Spec PR (if applicable)
- In the local spec repo (
../azure-rest-api-specs), create a branch and commit all spec changes. - Run
tsp format "*.tsp"in the TypeSpec project directory to ensure formatting passes CI validation. - Push the branch and create a PR against
Azure/azure-rest-api-specs. - Note the final commit SHA from the pushed branch.
- PR title:
Add csharp client customizations for <Service> migration
Step 3 — Create Generator PR (if applicable)
- In the SDK repo, create a branch containing only the generator changes under
eng/packages/http-client-csharp/. - Push and create a PR against
Azure/azure-sdk-for-net. - PR title:
[Generator] Fix <description> for <Service> migration - Include test project regeneration if the fix affects other SDKs (run
eng/packages/http-client-csharp/eng/scripts/Generate.ps1).
Step 4 — Create SDK Migration PR
- Update
tsp-location.yamlwith the final spec commit SHA from Step 2. - Switch back to remote mode — regenerate one final time without
LocalSpecRepoto verify CI-reproducible generation:
This is the only time the generation mode transitions from local back to remote.cd sdk\<service>\<PACKAGE_NAME>\src dotnet build /t:GenerateCode - Verify
dotnet buildstill passes. - Run pre-commit checks (Export-API, Update-Snippets, dotnet format).
- Commit all SDK changes and create a PR against
Azure/azure-sdk-for-net. - PR title:
<PACKAGE_NAME>: Migrate to TypeSpec (API version <API_VERSION>) - In the PR description, link to the spec PR and generator PR (if any) as dependencies.
Step 5 — Report Summary
After all PRs are created, report:
- Spec PR: Link and summary of decorators added.
- Generator PR: Link and summary of fixes (if any).
- SDK PR: Link and summary of migration changes.
- Manual follow-up: Any remaining items that need human review (naming decisions, breaking changes, etc.).
- Update
migration-status.mdwith all PR links and mark Phase 11 as done. Commit and push.
Phase 12 — Verify and Summarize
- Report summary of all completed steps and warnings.
- Note any
CodeGenTypeattributes needing manual review. - Remind user to review with
git diffbefore committing. - Suggest running the
pre-commit-checksskill. - Remove
migration-status.mdfrom the branch — it should not be included in the final merge tomain:git rm {LIBRARY_PATH}/migration-status.md git commit -m "Remove migration status file — migration complete" git push
Generator Bug Diagnosis
Before reporting a generator bug, ALWAYS:
- Remove/fix any customization files that could influence the generator.
- Re-run
[GENERATE]. - If the error persists with no customization influence → confirmed generator bug.
Do NOT manually fix Generated/ code. Do NOT silently suppress generator bugs with [CodeGenSuppress] or other workarounds.
Generator Bug Reporting
When a generator bug is confirmed, you MUST stop and report the bug to the user in detail. Do NOT silently work around it with [CodeGenSuppress] + custom implementation. The user needs to understand what is broken so they can decide how to proceed (fix the generator, file an issue, or explicitly choose a workaround).
Required information to present to the user:
- Summary — One-sentence description of the bug.
- Affected operations — Which TypeSpec operations / generated methods are broken, with their signatures.
- What the generated code looks like — Show the broken generated code snippet and explain what's wrong.
- What correct code would look like — Describe or show the expected behavior.
- Error messages — Full CS error codes and messages.
- Repro steps — Exact commands to reproduce (spec repo, commit, emitter version, tsp-location.yaml content).
- Confirmation it's not caused by customizations — State that you verified with zero custom files.
- Root cause hypothesis — Your best assessment of why the generator produces incorrect code.
After presenting the bug details, ask the user how they want to proceed:
- Option A: Fix the data-plane generator (emitter path:
eng/packages/http-client-csharp/) - Option B: File an issue and apply a
[CodeGenSuppress]workaround only with the user's explicit approval - Option C: Pause the migration and wait for a generator fix
Generator Fix Workflow
1. CONFIRM it's a generator bug (zero custom files, clean regeneration, same errors)
2. REPORT the bug to the user with full details (see Generator Bug Reporting above)
3. WAIT for user decision before proceeding
4. IF user chooses to fix generator:
- Edit under eng/packages/http-client-csharp/
- Rebuild the emitter (`npm run build`)
- Regenerate the affected SDK with `dotnet build /t:GenerateCode`
- Clean up stale custom workarounds
- Run Generate.ps1 to verify no regressions
5. IF user explicitly approves workaround:
- [CodeGenSuppress] + custom implementation
- Add a code comment documenting the generator bug and linking to any filed issue
Safety Rules
Hard Rules (Never Violate)
Never edit files under
Generated/— they are overwritten by codegen. This rule is enforced deterministically at three levels:- MCP tool guards:
regex_replacement,add_using_directive,remove_using_directive, andnullable_annotation_fixrefuse to write to Generated/ paths. - Snapshot verification:
build_and_classifyauto-verifies Generated/ file integrity every build when a snapshot exists (taken viasnapshot_generatedin Phase 5). - Post-cycle check: Call
verify_generated_unchangedwithautoRevert=trueafter the build-fix cycle to catch any modifications made outside MCP tools (e.g., viaedit,create, orpowershell).
- MCP tool guards:
Never hand-edit
metadata.json— it is auto-generated.Never use
tsp-client update— usedotnet build /t:GenerateCode.Never create or add entries to
ApiCompatBaseline.txt— this file must never be used to bypass breaking changes. Always mitigate ApiCompat errors through spec-side fixes (@@clientName) or SDK custom code shims inCustom/BackwardCompat/(see ApiCompat Error Handling in Phase 6 and error-reference.md). Do NOT suppress them via baseline files.Never disable ApiCompat or package validation in
.csproj— do NOT set<EnablePackageValidation>false</EnablePackageValidation>,<RunApiCompat>false</RunApiCompat>, or any other property that disables API compatibility checks. These checks exist to catch breaking changes and must always remain enabled. Fix the underlying breaking changes instead.Never bump the major version of an Azure SDK package.
Preserve git history — prefer renames over delete+create.
Never manually edit files under
src/Generated/— this is strictly forbidden. All generated code must come from the generator. If generated code has a bug (e.g., references a non-existent method, wrong type), fix it through:- TypeSpec decorators (
@@clientName,@@alternateType,@@access) inclient.tsp - Custom partial classes with
[CodeGenSuppress]insrc/Custom/to suppress the broken member and provide a corrected replacement - Generator bug fix if no decorator or customization can resolve it
Note:
[CodeGenSuppress]only takes effect when the custom files exist before code generation. After adding custom files, regenerate withdotnet build /t:GenerateCodeso the generator reads them and honors the suppression.- TypeSpec decorators (
Autonomous Mode (Default)
During the build-fix loop, Copilot operates autonomously. These actions are permitted without user confirmation:
- Spec changes: Adding
@@clientName,@@access,@@markAsPageable,@@alternateType, and other decorators toclient.tsp— these are safe, reversible, and csharp-scoped. - Custom code: Adding partial classes in the SDK custom code folder. Use
[CodeGenType]/[CodeGenSuppress]/[CodeGenMember]only when needed. - Deleting
autorest.mdafter extracting directives — git history preserves it. - Updating custom code to reference new generated type names.
- Removing or commenting out a problematic custom file that is causing errors in generated code — regenerate immediately after. Git history preserves the old version, and needed customizations can be re-introduced incrementally.
- Regenerating code using
dotnet build /t:GenerateCode. Always regenerate after modifying custom files that contain generator attributes. - Updating CHANGELOG.md and other metadata files.
Actions Requiring User Confirmation
These actions require explicit user approval (use ask_user):
- Modifying spec
.tspfiles beyondclient.tsp— e.g., changingmain.tsp, model definitions, or operation signatures. These affect all languages, not just C#. - Generator code changes that affect other SDKs — run
Generate.ps1to verify scope first. - Removing public API surface with no backward-compat option (true breaking change).
- Adding
ApiCompatBaseline.txtentries — this should almost never be done. - Deleting existing custom code files — may lose manually-written logic.
Escalation Criteria
Proceed without asking the user except when:
- Ambiguous fix — multiple equally valid approaches.
- Breaking API change with no backward-compat option.
- Generator fix affects other SDKs.
- 5 consecutive failed attempts for the same error.
- Error count increases after a fix.
Session End Rule
Always update and push migration-status.md before ending a session, even if the migration is incomplete. This ensures the next session (possibly on a different machine) can resume from the exact point where work stopped.
Fleet Mode Execution Strategy
Parallel Phase (explore agents)
- Agent 1: Find spec location and determine spec type
- Agent 2: Analyze existing SDK package structure
Sequential Phase (task/general-purpose agents)
- Create/update
tsp-location.yaml - Delete
autorest.mdif needed - Run code generation (initial_wait: 120+)
- Apply customizations
- Build error triage loop
- Final build and validate
Sub-Agent Strategy
- task agent — Run
dotnet build, collect errors, populate SQL table. - Batch spec fixes: explore → general-purpose → task (regenerate + rebuild).
- Batch custom code fixes: explore → general-purpose → task (rebuild).
- Generator fixes (one at a time): explore → general-purpose → task (rebuild emitter + regenerate + rebuild) → general-purpose (clean up stale workarounds) → task (rebuild).
Quick Reference — Do's and Don'ts
- Do NOT use
tsp-client update— usedotnet build /t:GenerateCode. - Do NOT hand-edit
metadata.json. - Match existing custom code folder convention.
- Don't blindly copy all renames from
autorest.md— only add@@clientNamefor names that actually cause build errors after generation. Check existing spec decorators to avoid duplicates. - Batch spec fixes, then rebuild — one spec fix can resolve 5–20 errors.
- Try spec-side fix (
@@access) before custom code ([CodeGenType]). - Finalize
tsp-location.yamlwith pushed spec commit before creating PR. - Run
CodeChecks.ps1before pushing:pwsh eng\scripts\CodeChecks.ps1 -ServiceDirectory <service> - Clean up stale custom workarounds after generator fixes.
- Always require
localSpecsPath— ask at migration start, not mid-flow. - Respect generation mode — remote by default, local after any
client.tsp/tspconfig.yamledit. Never mix modes within a migration.