Msbuild
.NET (Microsoft)OfficialPublished by .NET (Microsoft)
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured methodology for investigating MSBuild failures, performance bottlenecks, and authoring issues. It emphasizes evidence-based analysis using binary logs and incremental build checks, ensuring that repairs are targeted and minimal. The skill guides users through diagnosing failed builds, verifying incremental build contracts, and optimizing build performance without resorting to broad cleanups or unverified assumptions.
What it does
- Analyzes binary logs to identify root causes of build failures.
- Validates incremental build inputs, outputs, and skip conditions.
- Diagnoses performance bottlenecks in evaluation, compilation, and copying.
- Reviews MSBuild project files for anti-patterns and incorrect import contracts.
- Provides a framework for controlled performance experiments.
- Ensures build authoring changes preserve existing contracts and dependencies.
When to use it
- When a build fails and you need to identify the specific project instance or task responsible.
- When outputs are stale or build targets are incorrectly skipped.
- When you need to investigate slow build evaluation or compilation times.
- When reviewing or editing .proj, .props, .targets, or Directory.Build files.
- When you need to verify that custom build targets correctly handle incremental builds.
Advantages
- Promotes minimal, targeted repairs rather than broad, risky cleanups.
- Uses evidence-based analysis via binary logs instead of guesswork.
- Provides clear, repeatable patterns for performance benchmarking.
- Ensures build integrity by respecting existing contracts and evaluation order.
- Reduces build noise by correctly identifying cascading failures.
Usage examples
- “My build is failing with an obscure error. How do I investigate?”
- The agent will guide you to capture a binary log, replay it to isolate the specific failing task, and identify the root cause within the project structure.
- “Why is my custom target running even when inputs haven't changed?”
- The agent will help you audit the target's Inputs and Outputs, check for missing file registrations, and ensure input-set membership is correctly tracked.
- “How can I prove that changing a specific MSBuild property improves build time?”
- The agent will define a controlled two-arm experiment, comparing the baseline against the modified setting while keeping all other variables constant.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/dotnet/msbuild/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip msbuild.zip -d .agents/skills
Documentation by .NET (Microsoft)
MSBuild troubleshooting
For advice or static review, answer from the supplied XML, package facts, or measurements when they settle the question: no reference reads, checkout discovery, commands, or invented verification. For requested work, start at the supplied project or artifact; discover only what the next decision needs. Read a reference only for a specific unresolved question. References are not invocable skills.
Start with the task and evidence
Before editing, follow repository instructions and inspect the project/imports. Preserve the original command, working directory, SDK, configuration, target framework, runtime identifier, global properties, and target. Ask only for consequential information that cannot be discovered.
An existing .binlog can be the only available artifact. Analyze it before requesting a new
build; paths inside it do not prove a checkout exists locally.
For a mixed request, fix correctness before measuring successful builds, and keep authoring review separate from a performance experiment. A failing test assertion after a successful build is not an MSBuild failure.
Capture only: choose an unused artifact path under the supplied workspace, unless the user specified another artifact directory. Do not put the only deliverable in a shared/global temporary directory where workspace artifact collection will miss it, or stage/commit the log. Append the binary logger to the exact supplied command, run it once, and save its original exit code before checking the new log is nonempty. Report the artifact path and original exit code, then stop: no replay, diagnosis, or repair. Read capture details only for an unresolved wrapper, logger-option, toolset, or privacy question.
1. Troubleshoot build issues
Goal: identify the independent root causes and make the smallest repair, not a broad cleanup.
| Situation | Reference if needed | Action |
|---|---|---|
| A build failed and a binary log exists | binlog-failure-analysis | Replay the log with MSBuild; connect errors to the responsible project instance, target, and task. Separate root causes from cascading failures. |
| The failure needs evidence and no matching log exists | binlog-generation, then failure analysis | Capture the original invocation once and verify the artifact. Analyze it only when requested. |
| A project/build file has incorrect conditions, items, properties, or output paths | msbuild-antipatterns | Select the relevant catalog entries and check their exceptions before changing anything. |
| Imports or build hooks are missing, overwritten, or run in the wrong order | extension-points | Inspect the import contract, discovery order, and packed NuGet layout rather than hiding a required failure. |
| Outputs are stale or required work is skipped | incremental-build | Track input-set membership as well as file timestamps; apply the repair contract below. |
For build-wiring-only work, generator and source inputs are read-only throughout, not restored
after tests; edit the original only as the requested repair requires.
Allocate disposable scratch as a uniquely named directory outside the project tree, normally
the OS temporary directory from mktemp -d; a copy inside the project can be matched by SDK
default globs and break the later confirming build. Print the path once and write every copy, log,
and hash under it: no fixed leaf names, pointer files, pre-deleting, or globbing to recover it.
After the checks, remove exactly that path (never a pattern) and keep it in the report.
Copy the project/input tree and required imports; run every scenario that adds, deletes, empties,
or retimes inputs, or deletes obj/outputs, with the copy as working directory. A backup is not
a verification copy, nor is an original project already under a temporary path. Compare original
hashes before/after; equal final hashes do not excuse intermediate mutation. A confirming build of
the repaired original must not delete outputs. If the copy cannot reproduce the environment, report
blocked checks.
For input-set changes, prefer a persisted membership list written with WriteLinesToFile and
WriteOnlyWhenDifferent, plus actual files in Inputs. A path list is not a content hash.
Verify first/unchanged builds, deletion, older addition, empty inputs, and missing-output recovery
on the copy. End the repair report, however brief, with three labeled explanations, not only a checklist:
Cause: state both blind spots: a deleted input vanishes from the timestamp comparison; an added input older than the output never looks newer; either skips required regeneration.
Mechanism: identify the membership artifact and how its changes invalidate the output. Unchanged membership preserves skips; actual file timestamps still catch ordinary edits.
Checks: list passed, failed, and not-run separately with the command or path used. "None failed" requires every command to exit 0; an errored script, even if fixed and rerun, is listed failed with its cause. If the main scenario passed but a later assertion failed, report both; never summarize as verified.
After a repair, rerun the original failing scenario and check affected dependents. If only a log is available, provide an evidence-backed proposed fix and say it was not applied or rebuilt. Do not turn a targeted failure investigation into solution-wide authoring cleanup.
2. Diagnose and improve build performance
Goal: measure the original workload, not an easier replacement.
For advice, give the verdict, decisive facts, and a concrete next step with equivalent successful builds and required outputs. Do not re-prove a supplied timeline or read references to restate it. Do not infer warm/no-op state or assign any part of a confounded timing gap to restore, cache, or setting effects without isolating that cause.
| Supplied evidence | Decision | Smallest useful follow-up |
|---|---|---|
| Parallel target totals include overlapping dependency waits | Keep required references and parallelism. Cumulative waits can exceed both wall time and the duration of one dependency task; they do not prove CPU utilization or healthy scheduling. | Follow the supplied timeline to the dependency task holding up consumers. For Csc, inspect compiler inputs and invocation, analyzer/source-generator timing, and whether compilation was expected for this workload. Measure CPU/node utilization or I/O contention if unknown; do not infer them from wait totals. Validate any change with equivalent successful builds and required outputs. |
| Commands, restore policy, or starting input/output states differ | Reject attribution to the setting, not the possibility of a real gain. | Give one matched old-setting/new-setting experiment below, not a menu of different workloads or an invented setting name. |
| Repeated successful runs have matched conditions and outputs | Accept the observed improvement for that workload; use the same summary statistic for both arms: 100 * (old - new) / old. | Report sample count and observed elapsed-time ranges, not an invented percentage uncertainty interval. Then state residual limits: small sample, one workload/machine/SDK, what restored starting state does and does not control, and no independent rerun. Do not require a checkout to acknowledge the supplied result. |
When a minimal attribution experiment is needed, return one two-arm table, not alternatives:
Bind the treatment to the user's identified setting and old/new values. If unidentified, retain literal
<setting>,<old>,<new>placeholders. Differing commands are confounds, not setting names. Never substitute restore policy, Build/Rebuild, or cache state for an unidentified setting.Expand the full original baseline command in both arms. Only an explicitly identified command-line treatment may change its own argument; otherwise vary its configured value.
| Arm | Treatment, supplied or placeholder | Controlled invocation and workload |
|---|---|---|
| A | <setting> = <old> | Full original baseline command; input and starting outputs exactly as supplied |
| B | <setting> = <new> | Same full command, input, and starting outputs; only the named treatment differs |
Match non-treatment SDK, machine, configuration, instrumentation, target, restore policy, and input/output state exactly as supplied. Add no source edit the original workload lacked (keep one it had in both arms). Matching both arms is not enough if both replace the original workload. When they are controls, retain supplied Rebuild and timed restore; do not prime, warm, or move work outside the timer unless the original workload already did.
Alternate matched pairs; compare wall-time medians and observed ranges; check required outputs and diagnostics. Scope the result to that workload. The table is the only experiment; the last sentence states a validity check or residual limit, not an offer or variant.
For optimization work, establish the relevant successful baseline before changing one supported cause:
| Unresolved measurement | Reference if needed |
|---|---|
| Cold-output, changed-input, no-change, restore/cache state, or Build versus Rebuild | build-perf-baseline |
| Unknown task cost, compiler/analyzer/reference/restore work, or overlapping waits | build-perf-diagnostics |
| Pre-target globs, imports, property evaluation, or excess project instances | eval-performance |
| Copies/output I/O, unchanged copies, or a modified destination | copy-to-output-directory |
| Unexpected regeneration/skips, added/removed inputs, or missing outputs | incremental-build |
Stop when the suspected bottleneck is not supported by evidence, results fall within noise, or correctness regresses. Report missing measurements rather than manufacturing a complete baseline.
3. Review build authoring
Goal: preserve build contracts, not implicitly migrate frameworks, packages, languages, or project systems.
Check evaluation phases, not just import order. Outside targets, properties/imports are
evaluated before items. A framework set in the project body can be unavailable to an early
property condition in Directory.Build.props but available to an item condition in that same
file. Move only the premature property assignment after its dependency; do not relocate an
intentionally project-owned framework or a working item declaration. Preserve F# compile order
and inspect SDK defaults before adding switches to suppress supposedly duplicated items.
| Requested change | Reference if needed | Preserve |
|---|---|---|
| Review/clean up project files, shared properties, item declarations, or custom targets | msbuild-antipatterns | Intentional overrides, F# source order, required imports, package assets, and actual input/output contracts. |
| Review/improve hooks, imports, shared-file discovery, or NuGet build extensions | extension-points | Prior hooks, evaluation order, direct/transitive consumer behavior, and the packed layout. |
| Restructure generation or copying as part of cleanup | incremental-build and copy-to-output-directory, as applicable | First-build and no-change behavior, invalidation, generated-item registration, and output completeness. |
Record the existing contract; a matching catalog pattern alone is not a defect. Leave correct code alone. Validate authorized changes against original configurations, consumers, and affected build behavior.
Keep optional and required imports distinct, including inside aggregation files:
<Import Project="optional.targets" Condition="Exists('optional.targets')" />
<Import Project="required.targets" />The required import must load or fail, not silently skip. Explain why a condition on an entire mixed path list can suppress a valid required import; semicolon-separated imports themselves are supported. Check optional present/absent crossed with required present/absent, preserving order when both exist. When checks run, report the observed order and failure cases, not only "verification passed." Do not replace configurable paths with hard-coded values unnecessarily.
For a packaged forwarder, keep these bindings separate:
| Binding | Meaning |
|---|---|
NuspecFile | Selects the XML manifest, possibly outside the project; not a payload mapping. |
NuspecBasePath | Base directory for the manifest's file src lookups on disk; it neither locates nor selects the manifest. |
Mapping src / target | Reads the source file and emits its mapped archive path; does not implicitly ship the source path too. |
| Consumer import | Resolves from the importing file inside the package, not source, feed, or the consumer's framework. |
Do not substitute the manifest path for a mapping's src. For a no-change verdict, explain how
NuspecFile selection, NuspecBasePath src lookup, mapping, packed target, and relative import connect, and why guarding
a required import would hide a broken package. If supplied package facts settle this, stop:
no Exists guard, reorganization, or confirmatory rebuild/repack. Inspect artifacts only when
verification is requested or contents remain unknown.
Shared evidence and safety
For analysis, use MSBuild replay, never binary-file text parsing. Replay success and replay duration are not the recorded build's result or duration.
Resolve these files relative to this skill. Do not load the original plugin's skills or search other installations/checkouts. After a failed reference read, allow one listing of
referencesto distinguish a missing bundled file from a present-but-unreadable tool integration failure. Report the error and reduced coverage; do not retry through shell commands or other readers, and do not pretend unread guidance was followed. Never work around a policy denial.Do not clean outputs/caches, stop shared build servers, disable diagnostics, change SDKs, or serialize builds as generic fixes. Scope any necessary destructive experiment to approved, owned outputs. Keep binary and replayed logs local unless sharing is authorized.
Distinguish recorded facts from hypotheses. Missing source, log detail, tools, or platform support must be reported explicitly, not replaced with assumed values or invented verification.
Completion
Advice: verdict, decisive evidence, necessary next measurement; no manufactured verification. Repository work: cause/decision, minimal change (or no change), and exact successful, failed, and not-run checks. Performance claims also need comparable measurements and uncertainty. Scale length to the task, but never drop a required report field.
Files
- ATTRIBUTION.mdAdded by AutoGineer820 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md16.1 KiB
- references/antipatterns/additional-antipatterns.md12.8 KiB
- references/antipatterns/incremental-build-inputs-outputs.md3.0 KiB
- references/antipatterns/private-assets.md1.5 KiB
- references/binlog-failure-analysis.md4.8 KiB
- references/binlog-generation.md3.3 KiB
- references/build-perf-baseline.md19.0 KiB
- references/build-perf-diagnostics.md13.9 KiB
- references/copy-to-output-directory.md8.0 KiB
- references/eval-performance.md8.4 KiB
- references/extension-points.md13.4 KiB
- references/incremental-build.md17.2 KiB
- references/msbuild-antipatterns.md16.4 KiB
references/msbuild-antipatterns.md
MSBuild anti-pattern catalog
Based on the original msbuild-antipatterns skill. The numbered catalog retains its
smell, impact, and correction structure. Use relevant entries for troubleshooting or an
explicit authoring review, not as a mandate to rewrite every matching pattern.
Confirm evaluated behavior and exceptions before editing. Preserve framework/package versions, intentional overrides, item metadata, and public/output contracts. Legacy project-system migration is a separate task, not an automatic consequence of finding verbose XML.
For framework-conditioned shared XML, check property versus item evaluation in AP-21 before treating an early import as proof that both conditions are wrong.
AP-01: Exec for operations with built-in tasks
Smell: shell mkdir, copy, or del commands in a target.
Impact: shell-specific syntax, weak file-level logging, and more difficult incremental tracking. Built-in tasks integrate with MSBuild, but using one does not automatically make the containing target incremental.
<!-- BAD: shell-dependent file operations. -->
<Target Name="PrepareOutput">
<Exec Command="mkdir $(OutputPath)logs" />
<Exec Command="copy config.json $(OutputPath)" />
<Exec Command="del $(IntermediateOutputPath)*.tmp" />
</Target>
<!-- GOOD: preserve the intended operation with built-in tasks. -->
<Target Name="PrepareOutput">
<MakeDir Directories="$(OutputPath)logs" />
<Copy SourceFiles="config.json" DestinationFolder="$(OutputPath)" />
<ItemGroup>
<TempFiles Include="$(IntermediateOutputPath)*.tmp" />
</ItemGroup>
<Delete Files="@(TempFiles)" />
</Target>| Shell operation | MSBuild task |
|---|---|
mkdir | MakeDir |
copy / cp / recursive copy | Copy, with the intended item set |
del / rm | Delete, scoped to known owned files |
move / mv | Move |
| Write text | WriteLinesToFile |
| Update a timestamp | Touch |
Keep real external tools as tools, with correct quoting, platform support, and error handling. Preserve copy/reset semantics and never widen the deletion scope during this conversion.
AP-02: Unquoted string comparisons
Smell: Condition="$(Foo) == Bar".
Impact: empty values or values containing spaces/special characters can change parsing.
<!-- BAD -->
<PropertyGroup Condition="$(Configuration) == Release">
<Optimize>true</Optimize>
</PropertyGroup>
<!-- GOOD -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
</PropertyGroup>Quote both string operands. Do not flag valid boolean/function conditions merely because they are not string comparisons.
AP-03: Hardcoded absolute paths
Smell: machine-specific tool or import paths.
Impact: builds fail on another checkout, machine, or operating system.
<!-- BAD -->
<PropertyGroup>
<ToolPath>C:\tools\mytool\mytool.exe</ToolPath>
</PropertyGroup>
<Import Project="C:\repos\shared\common.props" />
<!-- GOOD: RepoRoot must be defined by the repository. -->
<PropertyGroup>
<ToolPath>$(MSBuildThisFileDirectory)tools\mytool\mytool.exe</ToolPath>
</PropertyGroup>
<Import Project="$(RepoRoot)eng\common.props" />| Property/function | Meaning |
|---|---|
MSBuildThisFileDirectory | Directory of the file defining the current import/logic |
MSBuildProjectDirectory | Directory of the project being built |
GetDirectoryNameOfFileAbove / GetPathOfFileAbove | Find a repository marker or parent file |
NormalizePath / NormalizeDirectory | Combine and normalize path segments |
Resolve relative paths against the intended owner, not an incidental caller working directory.
AP-04: Restating SDK defaults
Smell: properties repeat the selected SDK's defaults.
Impact: noise can obscure real overrides and accidentally pin behavior across SDK changes.
<!-- Potentially redundant for a library named MyLib with these SDK defaults. -->
<PropertyGroup>
<OutputType>Library</OutputType>
<EnableDefaultItems>true</EnableDefaultItems>
<EnableDefaultCompileItems>true</EnableDefaultCompileItems>
<RootNamespace>MyLib</RootNamespace>
<AssemblyName>MyLib</AssemblyName>
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<!-- Keep the settings that actually express the project's contract. -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>Verify the actual SDK/imports, configurations, naming, and intentional pins first. A setting that matches today's default can still deliberately preserve behavior; do not remove it blindly.
AP-05: Redundant file lists in SDK-style projects
Smell: explicit C#/VB source includes duplicate enabled SDK default items.
Impact: duplicate items, merge noise, and source additions that can be missed when defaults have instead been disabled.
<!-- BAD when default Compile items already contain these files. -->
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Services\MyService.cs" />
</ItemGroup>
<!-- GOOD: keep only intentional exclusions or metadata. -->
<ItemGroup>
<Compile Remove="LegacyCode\**" />
<Compile Update="Services\MyService.cs" Visible="false" />
</ItemGroup>Exceptions: legacy projects, projects with default items intentionally disabled, linked files
outside default globs, generated files, and items with special metadata need their actual contract
preserved. Update modifies an existing item; it does not add a missing one.
F# is order-dependent. Keep explicit .fsproj compile items in dependency order. An .fsi
signature must immediately precede its matching .fs; the entry point is last. Do not apply C#
implicit-glob cleanup to F#.
The standard F# SDK already disables default compile items. Do not prescribe
EnableDefaultCompileItems=false as a required repair without evidence that the selected
SDK/imports actually enable unwanted defaults.
AP-06: Package DLL HintPaths instead of package references
Smell: package assemblies referenced through a legacy packages directory.
Impact: the project depends on a fixed restored layout instead of PackageReference asset selection. This is a migration candidate, not proof that existing packages.config restore is broken.
<!-- Legacy package reference. -->
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\netstandard2.0\Newtonsoft.Json.dll</HintPath>
</Reference>
<!-- PackageReference replacement, when migration is authorized and compatible. -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />Preserve package versions, conditions, content/build assets, and restore behavior. Do not remove
GAC, local, or vendor assembly references just because they use Reference or HintPath.
AP-07: Build-only packages exposed to consumers
Smell: a local analyzer/tool becomes an unwanted dependency or build asset of consumers.
Impact: consumers can receive implementation-only dependencies or transitive build behavior.
Inspect effective metadata and package assets; absence of a literal PrivateAssets attribute
does not prove all analyzer assets propagate.
Use PrivateAssets="all" for dependencies intended to remain entirely private. Preserve
intentionally public/transitive build contracts. See
private-assets.md for examples and exceptions.
AP-08: Copy-pasted shared properties
Smell: the same property block is repeated across several projects.
Impact: inconsistent updates and unclear ownership.
<!-- Directory.Build.props at the intended shared scope. -->
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>Move only genuinely shared values. Preserve project overrides and property evaluation timing. Only the nearest Directory.Build file is automatically discovered; see extension-points before changing hierarchy.
AP-09: Unintended package-version drift
Smell: projects unintentionally use different versions of the same package.
Impact: divergent behavior, conflicts, and difficult dependency maintenance.
<!-- ProjectA.csproj -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<!-- ProjectB.csproj -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />Central Package Management can consolidate versions when that migration is requested; see the NuGet guidance. Keep intentional framework/project differences. Do not silently upgrade packages or centralize the whole repository while fixing an unrelated build.
AP-10: Monolithic targets
Smell: one target mixes generation, copying, signing, and other unrelated operations.
Impact: the steps cannot be scheduled, diagnosed, or skipped independently.
<!-- BAD: unrelated work in one target. -->
<Target Name="PrepareRelease" BeforeTargets="Build">
<WriteLinesToFile File="version.txt" Lines="$(Version)" Overwrite="true" />
<Copy SourceFiles="LICENSE" DestinationFolder="$(OutputPath)" />
<Exec Command="signtool sign /f cert.pfx $(OutputPath)*.dll" />
</Target>
<!-- GOOD: separate responsibilities with their own contracts. -->
<Target Name="WriteVersionFile" BeforeTargets="CoreCompile">
<MakeDir Directories="$(IntermediateOutputPath)" />
<WriteLinesToFile File="$(IntermediateOutputPath)version.txt"
Lines="$(Version)" Overwrite="true" WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)version.txt" />
</ItemGroup>
</Target>
<Target Name="CopyLicense" AfterTargets="CopyFilesToOutputDirectory">
<Copy SourceFiles="LICENSE" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true">
<Output TaskParameter="CopiedFiles" ItemName="FileWrites" />
</Copy>
</Target>
<Target Name="SignAssemblies" AfterTargets="Build" DependsOnTargets="CopyLicense"
Condition="'$(SignAssemblies)' == 'true'">
<ItemGroup>
<AssemblyFiles Include="$(OutputPath)*.dll" />
</ItemGroup>
<Error Condition="'@(AssemblyFiles)' == ''"
Text="Signing was requested, but no assemblies were found in '$(OutputPath)'." />
<Exec Command="signtool sign /f cert.pfx "%(AssemblyFiles.Identity)"" />
</Target>The cheap version writer intentionally checks the value each build: Version can change through
global properties without any project-file timestamp changing. A project-file-only Inputs
declaration would incorrectly reuse stale content. For expensive generation, model value changes
with a stable input fingerprint as in AP-11's example.
Preserve original target order, supported platforms, required tools, and copy semantics.
SkipUnchangedFiles is appropriate only when its timestamp/size heuristic satisfies the contract.
Register the copied destinations in FileWrites before MSBuild records them for a subsequent
Clean. Hooking CopyLicense after CopyFilesToOutputDirectory does this even for unchanged
copies; registering them in an AfterTargets="Build" target would be too late.
Discover the assemblies inside the signing target, after the build has produced them, rather
than with an evaluation-time glob. This retains the original output-directory DLL selection.
AP-11: Untracked artifact-producing targets
Smell: expensive file generation runs on every no-change build because its inputs/outputs are not modeled, or skips despite a relevant change.
Impact: repeated work or stale outputs. Inputs must cover actual files and relevant property, configuration, generator, and import changes.
See incremental-build-inputs-outputs.md for a correctness-oriented example, and incremental-build for full diagnosis. Cheap validation, orchestration, and item-discovery targets may legitimately run without file-based outputs; do not invent a stamp to suppress necessary work.
AP-12: Defaults defined after their consumers
Smell: a default is set in a late import after another file already used it.
Impact: consumers observe an empty or unintended earlier value.
<!-- custom.props: an early, overridable default. -->
<PropertyGroup>
<MyToolVersion Condition="'$(MyToolVersion)' == ''">2.0</MyToolVersion>
</PropertyGroup>
<!-- custom.targets: execution using that setting. -->
<Target Name="RunMyTool">
<Exec Command="mytool --version $(MyToolVersion)" />
</Target>.props normally carries early defaults; .targets normally carries later build logic.
Timing matters more than the suffix: a value depending on a project-defined property must not
move earlier than that property exists. See AP-21 in
additional anti-patterns.
AP-13: Missing guards on optional imports
Smell: an optional developer/environment override is imported unconditionally.
Impact: a fresh checkout fails because a file that is optional by contract is absent.
<!-- Guard an optional override. -->
<Import Project="$(RepoRoot)eng\local.props"
Condition="Exists('$(RepoRoot)eng\local.props')" />Required imports must fail when missing. Do not add an existence guard to hide a missing SDK, required build file, or broken package contract.
NuGet forwarders need a packed-layout check. Files in build/buildTransitive can import
paths that do not exist in source but are created by .nuspec mappings, PackagePath metadata,
or SDK pack conventions. First inspect the effective NuspecFile, NuspecBasePath, and
NuspecProperties, including imports and global-property overrides. Follow the configured
nuspec even outside the project tree and resolve its mappings with those settings. Only when
no custom nuspec is configured, inspect nearby .nuspec files in the project and its immediate
parent as a bounded fallback. Inspect the project's packaging items too; do not search unrelated
ancestors or treat an unavailable configured nuspec as proof of a broken import.
Only flag the target if it is absent from both applicable source and projected package layouts.
See source tree vs packed layout.
For per-TFM forwarders, derive the matched folder from the file's own location rather than the
consumer's TargetFramework; see the forwarding chain.
AP-14: Backslashes where path normalization does not apply
Smell: Windows separators in a path intended to reach a non-MSBuild consumer on another OS.
Correctness issue: raw shell strings, generated source/response files, or custom tasks that use OS APIs directly may interpret backslashes differently.
Not automatically a correctness issue: Import paths, item globs, and file paths consumed by
built-in tasks use MSBuild path handling. An ordinary backslash-style import is not, by itself,
a Linux failure.
<!-- A raw Windows shell/tool path needs actual platform support and quoting. -->
<Exec Command="$(MSBuildThisFileDirectory)tools\release\sign.exe $(OutputPath)" />
<!-- MSBuild handles this import path. Do not report a defect solely for its separators. -->
<Import Project="$(MSBuildThisFileDirectory)..\..\build\common.props" />Trace the consumer before assigning severity. MSBuildThisFileDirectory already ends with a
separator; use path functions when constructing paths rather than concatenating arbitrary strings.
AP-15: Unexpected property overrides across scopes
Smell: a shared value is unexpectedly replaced by another unconditional assignment.
Impact: last-write behavior is difficult to explain and can alter output paths or options.
<!-- Directory.Build.props: a default that permits an existing value. -->
<PropertyGroup>
<OutputPath Condition="'$(OutputPath)' == ''">bin\custom\</OutputPath>
</PropertyGroup>Trace assignments and consumers first. A project overriding a repository default can be intentional, and command-line global properties have different override semantics. Do not add conditions everywhere or change path-defining properties too late for their SDK/NuGet consumers.
For AP-16 through AP-23 and the quick-reference checklist, read additional-antipatterns.md.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- .NET (Microsoft)
- Publisher
- .NET (Microsoft) (Official)
- Source
- dotnet/skills · plugins/dotnet/skills/msbuild · commit 96c3d68
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by .NET (Microsoft).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download msbuild.zip