target-authoring — Claude Code skill: MSBuild target patterns
A skill for writing custom MSBuild targets: canonical patterns plus fixes for anti-patterns like overwriting DependsOn chains.
npx skills add dotnet/skills --skill target-authoring -a claude-codeWhat target-authoring does
The skill collects canonical patterns from Microsoft.Common.CurrentVersion.targets in the MSBuild repository. It covers the three-level Build → CoreBuild → After chain, when to reach for DependsOnTargets versus BeforeTargets and AfterTargets, and how to extend a chain through $(XxxDependsOn).
There is a section on Returns versus Outputs. Put Outputs on a query target such as GetTargetPath and MSBuild treats it as up to date, handing back stale data. Inter-project communication wants Returns; incrementality wants Outputs.
A full custom target template is included: the dependency chain, empty extensibility hooks, Inputs/Outputs, FileWrites registration and input validation. Incremental build tuning, parallelization and general anti-patterns live in separate skills, and non-MSBuild build systems are out of scope.
What it helps with
- repair a broken SDK target chain in
Directory.Build.targets - append a custom target to
CompileDependsOnwithout dropping SDK targets - decide between
ReturnsandOutputsfor a query target - stop needless rebuilds caused by missing
Inputs/Outputs - register generated files in
FileWritesso clean works
Example prompts for the agent
- Check my
Directory.Build.targets— am I overwriting the SDK target chain? - Write a custom target with Inputs/Outputs and FileWrites registration
- Why does my query target return stale results? Explain Returns vs Outputs
How to install target-authoring
1Skills CLI (Vercel)
npx skills add dotnet/skills --skill target-authoring -a claude-code2Manually: put the files in place
~/.claude/skills/target-authoring/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/target-authoring/
1Skills CLI (Vercel)
npx skills add dotnet/skills --skill target-authoring -a codex2Manually: put the files in place
.agents/skills/target-authoring/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add dotnet/skills --skill target-authoring -a cursor2Manually: put the files in place
.cursor/skills/target-authoring/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install dotnet/skills target-authoring2Manually: put the files in place
.github/skills/target-authoring/SKILL.md1Skills CLI (Vercel)
npx skills add dotnet/skills --skill target-authoring -a gemini-cli2Platform CLI
gemini skills install https://github.com/dotnet/skills --path plugins/dotnet-msbuild/skills/target-authoringManually: put the files in place
.devin/skills/target-authoring/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add dotnet/skills --skill target-authoring -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about target-authoring
How do I install target-authoring in Claude?
Run npx skills add dotnet/skills --skill target-authoring -a claude-code. The skill lands in ~/.claude/skills/target-authoring/SKILL.md.
What does target-authoring do?
It supplies canonical patterns for custom MSBuild targets and flags anti-patterns: overwritten DependsOn chains, mixed-up Returns and Outputs, missing FileWrites.
How is it different from msbuild-antipatterns?
target-authoring is about authoring and repairing targets themselves. General MSBuild anti-patterns go to msbuild-antipatterns, incremental build tuning to incremental-build, parallelization to build-parallelism.
Source text
SKILL.md · MIT
---
name: target-authoring
description: "Canonical patterns for writing custom MSBuild targets. USE FOR: diagnosing and fixing custom target authoring anti-patterns; broken SDK target chains across files (e.g., Directory.Build.targets silently redefining SDK targets); targets that replace CompileDependsOn instead of extending it with $(CompileDependsOn); query targets returning stale results from Outputs vs Returns misuse; missing Inputs/Outputs causing unnecessary rebuilds; missing FileWrites registration. Covers DependsOnTargets vs BeforeTargets vs AfterTargets, the Build→CoreBuild three-level pattern, and the $(XxxDependsOn) chain-extension pattern. DO NOT USE FOR: incremental build tuning (use incremental-build), parallelization (use build-parallelism), general anti-patterns (use msbuild-antipatterns), non-MSBuild build systems."
license: MIT
---
# Custom Target Authoring Patterns
Canonical patterns from `Microsoft.Common.CurrentVersion.targets` in the MSBuild repository.
## The Three-Level Target Chain
Every major entry point (Build, Rebuild, Clean) delegates to a **property** listing its dependencies, which chains through Before → Core → After:
```xml
<PropertyGroup>
<BuildDependsOn>
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
<Target Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!-- Empty extensibility targets — users override these -->
<Target Name="BeforeBuild" />
<Target Name="AfterBuild" />
```
`CoreBuild` delegates to `$(CoreBuildDependsOn)` and includes error handlers:
```xml
<Target Name="CoreBuild" DependsOnTargets="$(CoreBuildDependsOn)">
<OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent"
Condition="'$(RunPostBuildEvent)' == 'Always'" />
<OnError ExecuteTargets="_CleanRecordFileWrites" />
</Target>
```
### Rules
- Delegate to a property (`DependsOnTargets="$(MyTargetDependsOn)"`), not hardcoded targets.
- `OnError` goes inside the orchestrating target to ensure cleanup runs even on failure.
- Empty Before/After targets are extensibility points. Users override them; SDKs never put logic in them.
## Chain Extension — Append, Never Overwrite
When adding a custom target to an existing chain, **append** to the `DependsOn` property:
```xml
<!-- GOOD: Append to existing chain -->
<PropertyGroup>
<CompileDependsOn>$(CompileDependsOn);MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>
<!-- BAD: Overwrites the entire chain, dropping SDK targets -->
<PropertyGroup>
<CompileDependsOn>MyCodeGenTarget</CompileDependsOn>
</PropertyGroup>
```
## DependsOnTargets vs BeforeTargets vs AfterTargets
| Mechanism | Defined in | Best for |
|---|---|---|
| `DependsOnTargets` | The target that needs deps | Target explicitly requires others |
| `BeforeTargets` | The injecting target | Insert before a target you don't own |
| `AfterTargets` | The injecting target | Insert after a target you don't own |
Validation targets use `BeforeTargets` to intercept all entry points:
```xml
<Target Name="_CheckForInvalidConfigurationAndPlatform"
BeforeTargets="$(BuildDependsOn);Build;$(RebuildDependsOn);Rebuild;$(CleanDependsOn);Clean">
</Target>
```
**Rules:**
- Use `DependsOnTargets` when your target needs specific prerequisites.
- Use `BeforeTargets`/`AfterTargets` when injecting into a pipeline you don't own.
- Prefer `BeforeTargets="CoreCompile"` over modifying `$(CompileDependsOn)` when you don't control the targets file.
## Returns vs Outputs
```xml
<!-- Build returns items for consumption by referencing projects -->
<Target Name="Build"
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
<!-- GetTargetPath is a lightweight query target -->
<Target Name="GetTargetPath" Returns="@(TargetPathWithTargetPlatformMoniker)" />
```
- **`Returns`** specifies what the MSBuild task receives when calling this project. Use for inter-project communication.
- **`Outputs`** on inner targets is for incrementality (timestamp checks). Use for up-to-date detection.
- Never mix the two purposes. Query targets (`GetTargetPath`, `GetTargetFrameworks`) should use `Returns`, not `Outputs`.
## Target Naming Conventions
| Pattern | Meaning | Example |
|---|---|---|
| `_PrefixedName` | Internal/private target | `_TimeStampBeforeCompile` |
| `CoreXxx` | The actual implementation | `CoreBuild`, `CoreCompile` |
| `BeforeXxx` / `AfterXxx` | Empty extensibility hooks | `BeforeBuild`, `AfterCompile` |
| `PrepareXxx` | Setup/validation phase | `PrepareForBuild` |
| `ResolveXxx` | Discovery/resolution phase | `ResolveReferences` |
| `GetXxx` | Lightweight query (no side effects) | `GetTargetPath` |
## Complete Custom Target Template
```xml
<!-- 1. Define the DependsOn chain for extensibility -->
<PropertyGroup>
<MyFeatureDependsOn>
_ValidateMyFeatureInputs;
BeforeMyFeature;
CoreMyFeature;
AfterMyFeature
</MyFeatureDependsOn>
</PropertyGroup>
<!-- 2. Outer target with Returns for inter-project communication -->
<Target Name="MyFeature"
DependsOnTargets="$(MyFeatureDependsOn)"
Returns="@(MyFeatureOutput)" />
<!-- 3. Empty extensibility points -->
<Target Name="BeforeMyFeature" />
<Target Name="AfterMyFeature" />
<!-- 4. Core implementation with Inputs/Outputs for incrementality -->
<Target Name="CoreMyFeature"
Inputs="$(MSBuildAllProjects);@(MyFeatureInput)"
Outputs="$(IntermediateOutputPath)myfeature.generated.cs">
<Exec Command="my-tool.exe -o $(IntermediateOutputPath)myfeature.generated.cs" />
<!-- 5. Register outputs for clean tracking -->
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)myfeature.generated.cs" />
<FileWrites Include="$(IntermediateOutputPath)myfeature.generated.cs" />
</ItemGroup>
</Target>
<!-- 6. Validation target runs first in the dependency chain -->
<Target Name="_ValidateMyFeatureInputs">
<Error Text="MyFeatureInput items are required."
Condition="'@(MyFeatureInput)' == ''" />
</Target>
```
## Common Pitfalls
- **Overwriting `DependsOn` properties** drops SDK targets silently. Always include `$(ExistingProperty)` when appending.
- **Using `Outputs` on query targets** causes MSBuild to skip them when "up to date," returning stale data. Use `Returns`.
- **Defining targets in `.props`** means `BeforeTargets` on SDK targets have nothing to hook into yet. Move targets to `.targets`.
- **Forgetting `OnError`** in orchestrating targets means file tracking fails on build errors, breaking subsequent incremental builds.
© dotnet · MIT · dotnet/skills