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://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/build-perf-baseline.md
Build Performance Baseline & Optimization
Source: original build-perf-baseline skill.
Overview
Before optimizing a build, you need a baseline. Without measurements, optimization is guesswork. This reference covers establishing baselines and applying systematic, measured optimization techniques.
Related references:
Build performance diagnostics - binlog-based bottleneck identification
Incremental builds - Inputs/Outputs and up-to-date checks
Parallel build tuning - parallelism and critical paths
Evaluation performance - glob and import-chain optimization
Step 1: Establish a Performance Baseline
Measure three scenarios to understand where time is spent. Reuse matching existing artifacts first; use binlog generation for capture and privacy guidance. Discover the repository's actual build entry point and retain its configuration, TFM/RID, properties, SDK/toolset, node count, and restore policy in the examples below.
The binlog filenames below are illustrative. Choose an unused explicit filename for every capture; do not overwrite existing evidence.
Record those settings, source revision/local changes, machine resources, cache state, and exact input edit. Compare successful builds of the same scenario, not a failed build, a smaller solution, or differently instrumented runs.
For an advisory before/after claim, first assess the supplied measurements. A
Rebuild including restore and an unchanged ordinary build without restore do
not isolate an MSBuild setting. The corrective experiment compares the old
setting value with the new value, keeping the invocation, restore policy,
input edit, starting output state, and instrumentation equivalent in both arms.
Do not infer the unspecified setting from a command-line difference.
Conversely, accept comparable repeated measurements as evidence for their stated workload, with sample-size and noise limits. A checkout, a fresh binlog, or measurements of every other build scenario are not prerequisites for answering a self-contained advisory question.
Cold Build (First Build)
No previous output exists for the selected build. An end-to-end cold-output measurement includes restore, compilation, and all required work.
Use an agreed disposable workspace or explicitly authorized cleanup of known
generated paths. Inspect custom Clean behavior and shared outputs first. Do not
recursively delete every directory named bin or obj, clear shared NuGet caches,
or stop shared build servers. If safe preparation is unavailable, report the cold
scenario as unmeasured.
# After preparing the agreed cold-output state
dotnet build -m "-bl:cold-build.binlog"Record whether packages, compiler servers, and filesystem caches are already warm. Removing build outputs does not make those caches cold.
Warm Build (Incremental Build)
Build output exists and some files have changed. This measures how well incremental building handles a specific change.
dotnet build -m
if ($LASTEXITCODE -ne 0) { throw "Initial build failed; no valid warm baseline." }
# Make one recorded, reversible source/input change, then build normally
dotnet build -m "-bl:warm-build.binlog"A timestamp-only touch, implementation edit, and public-API edit can propagate differently. Use the same kind of change before and after an optimization.
No-Op Build (Nothing Changed)
Build output exists and nothing has changed. Expensive work whose inputs and outputs are up to date should skip. A slow no-op can still be caused by evaluation, restore checks, reference resolution, or orchestration rather than broken compilation incrementality.
dotnet build -m
if ($LASTEXITCODE -ne 0) { throw "Initial build failed; no valid no-op baseline." }
dotnet build -m "-bl:noop-build.binlog"Label this warm no-change scenario separately from a warm changed-input build.
Do not use Rebuild, -t:Rebuild, or --no-incremental for the second build:
forced full work is a separate benchmark, not a no-op or proof of cold caches.
What Good Looks Like
| Scenario | Expected behavior |
|---|---|
| Cold build | Complete required outputs, including compilation and required generation. This is the full-work baseline for the recorded cache state. |
| Warm build | Changed inputs and affected dependencies invalidate the appropriate work; unrelated recompilation needs explanation. |
| No-op build | Up-to-date expensive compilation/generation skips. Under roughly 5 seconds for small repositories or 30 seconds for large ones can be useful triage hints, not universal budgets. |
Red flags to investigate, not automatic diagnoses:
No-op build over 30 seconds: distinguish evaluation, restore, copying, and unexpected execution before applying incremental fixes.
Warm build recompiles everything: inspect the input/API change and dependency chain; do not assume every downstream rebuild is unnecessary.
Long cold restore: investigate feed/network work and cache state, not just package-cache problems.
Recording Baselines
Prefer repeated measurements, such as three runs per scenario, and report their median and spread. Re-establish the correct cold or warm state before each run. Do not mix cold and warmed timings or silently discard inconvenient outliers.
| Scenario | Before median [range] | After median [range] | Improvement |
|---|---|---|---|
| Cold build, restore included/excluded | Not measured | Not measured | Seconds and percent |
| Warm build, named input edit | Not measured | Not measured | Seconds and percent |
| No-op build, unchanged command | Not measured | Not measured | Seconds and percent |
Fill this table with actual results and artifact paths. Percentage improvement is
(before - after) / before * 100; a negative result is a regression. Change one
variable at a time and retain the same success/output checks. Use distinct log
names for each run and for before/after results so baseline evidence is not
overwritten.
Step 2: Artifacts Output Layout
UseArtifactsOutput, introduced in .NET SDK 8, centralizes output layout.
It can address output-management and collision problems and simplify CI caching;
it does not itself make compilation faster or validate a cache.
Enabling Artifacts Output
<!-- Directory.Build.props -->
<PropertyGroup>
<UseArtifactsOutput>true</UseArtifactsOutput>
</PropertyGroup>Before vs After
Illustrative single-target layout:
# Traditional layout
src\
MyLib\
bin\Debug\net8.0\MyLib.dll
obj\Debug\net8.0\...
MyApp\
bin\Debug\net8.0\MyApp.dll
# Artifacts layout
artifacts\
bin\MyLib\debug\MyLib.dll
bin\MyApp\debug\MyApp.dll
obj\MyLib\debug\...
obj\MyApp\debug\...Benefits
Separated outputs: project/configuration pivots reduce accidental overlap; check duplicate project names and custom path/pivot overrides.
Easier cache management: a common artifacts root is easier to collect, but cache keys must still cover the relevant inputs, options, SDK, and platform.
Simpler ignore rules: the generated artifacts root can be ignored centrally.
Multi-targeting: SDK pivots distinguish TFMs/RIDs where needed; inspect the evaluated paths instead of assuming one fixed subdirectory pattern.
Customizing
<PropertyGroup>
<ArtifactsPath>$(MSBuildThisFileDirectory)output</ArtifactsPath>
</PropertyGroup>The CLI's --artifacts-path also requires SDK 8+. Keep restore, build, test,
pack/publish, scripts, and cleanup consistent with the new locations. Verify the
supported configuration/TFM/RID matrix and file ownership before adopting the
layout. A target framework change alone does not enable this SDK feature.
Step 3: Deterministic Builds
Deterministic compilation produces identical compiler outputs for identical compiler inputs. This supports caching and reproducibility, but does not make every custom build step or package reproducible automatically.
Enabling Deterministic Builds
<!-- Directory.Build.props -->
<PropertyGroup>
<!-- Already enabled by default in modern .NET SDK projects -->
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>Use the repository's actual CI signal; not every runner supplies CI=true.
What Deterministic Affects
Removes time-dependent variation from compiler output; it does not simply zero every PE timestamp field.
Makes compiler outputs repeatable for the same complete inputs.
Works with source/path normalization for reproducible PDBs; deterministic compilation alone does not normalize arbitrary machine-specific paths.
Why It Matters for Performance
Build caching: deterministic outputs can be reused when the complete cache key and required artifacts are correct.
CI optimization: correct input tracking can avoid rebuilding unchanged work.
Distributed builds: cross-machine reuse also requires compatible toolsets, paths, options, dependencies, and generated content.
Verify reproducibility of the required artifacts. Neither these properties nor NuGet lock files create a compiled-output cache or excuse untracked inputs.
Step 4: Dependency Graph Trimming
Removing genuinely unnecessary references can reduce graph/resolution work and, when it removes a critical dependency, shorten the critical path.
Audit the Dependency Graph
Inspect existing binlogs for project references, global-property variants, build times, and waits. If a new capture is needed:
dotnet build "-bl:graph.binlog"Use local replay and
performance diagnostics to follow the actual work.
Inclusive ResolveProjectReferences time includes waiting for dependencies; it
is not all reference-resolution CPU work.
Techniques
Remove Redundant Transitive References
<!-- Before: Core also references Utils -->
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
<!-- Candidate only after proving the direct Utils edge is unnecessary -->
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>Transitive reachability alone is insufficient. A direct reference may express
direct API use, repository policy, or important metadata. Verify compile/runtime
dependencies and pack/publish behavior across supported TFMs/configurations.
Removing App -> Utils does not necessarily shorten App -> Core -> Utils.
Build-Order-Only References
When a project must build first but its assembly is not a compiler reference:
<ProjectReference Include="..\CodeGen\CodeGen.csproj"
ReferenceOutputAssembly="false" />This preserves the build-order dependency. It does not remove the scheduling edge or make the projects independent.
Prevent Transitive Asset Flow
When a dependency is an implementation detail that should not be exposed through the relevant NuGet asset/pack contract:
<ProjectReference Include="..\InternalHelpers\InternalHelpers.csproj"
PrivateAssets="all" />Inspect restored assets, generated package dependencies, and consuming projects.
PrivateAssets is not a generic "do not build" or "remove the compiler reference"
switch, and this metadata alone does not establish a performance improvement.
Disable Transitive Project References
For deliberately explicit-only dependency management in supporting SDK builds:
<PropertyGroup>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>Caution: this requires explicit references to consumed dependencies. Consider it only for measured transitive-closure overhead, with consumer validation. It does not eliminate required project build ordering.
Step 5: Static Graph Builds (/graph)
Static graph mode constructs the project graph before building, allowing scheduling from the declared dependencies.
Enabling Graph Build
dotnet build -graph
dotnet build -graph "-bl:graph-build.binlog"Benefits
Scheduling: knowing the complete graph can improve parallel scheduling.
Isolation: project isolation is a separate contract, selected with
-isolateProjects;/graphalone does not guarantee it.Caching potential: results-cache options are separate features with their own isolation/correctness requirements, not an automatic graph-build cache.
When to Use
| Scenario | Recommendation |
|---|---|
| Large multi-project solution, for example 20+ projects | Measure graph mode as a candidate; project count is not proof of a benefit. |
| Small solution, for example fewer than 5 projects | Extra graph construction may outweigh scheduling gains; measure rather than assume. |
| CI builds | Try when dependencies are statically discoverable and output isolation is sound. |
| Local development | Compare both modes in the same cold, changed-input, or no-change scenario. |
Troubleshooting Graph Build
All relevant project dependencies must be statically discoverable. References
created inside targets or hidden in programmatic MSBuild calls can violate
that model. Inspect the actual emitted error rather than assuming one error code.
Declare ProjectReference items during evaluation where that preserves their
semantics. Do not move execution-dependent logic blindly, drop dependencies, or
disable dependency builds to make graph construction pass. If the graph cannot
represent the build correctly, retain the normal build.
Step 6: Parallel Build Tuning
MaxCpuCount
# Permit up to the logical processor count
dotnet build -m
# Explicit budget, useful on shared CI agents
dotnet build -m:4
# MSBuild.exe syntax
msbuild -m:8 .\MySolution.slnRaw MSBuild defaults to one node without -m; the dotnet build driver can
supply switches itself. Inspect the actual invocation and record an explicit
budget when comparing runs. Respect memory limits, CPU quotas, and other users.
Identifying Parallelism Bottlenecks
In recorded project-instance and scheduling evidence, look for:
Long sequential chains: projects waiting on required predecessors.
Uneven load: idle nodes while others remain busy.
Single-project bottleneck: one long project on the critical path.
Name the chain with durations, for example Core -> Api -> Web -> Tests.
Aggregate target/task summaries alone cannot establish node utilization: totals
can overlap, nest, or include waits. If replay lacks sufficient scheduling detail,
report that gap rather than inventing utilization percentages.
For independent requests in a custom orchestration target, enable parallelism
on the MSBuild task as well as providing multiple nodes:
<MSBuild Projects="@(ProjectsToBuild)" Targets="Build" BuildInParallel="true" />Reducing the Critical Path
Split a measured large bottleneck only if the resulting work can run independently.
Remove proven unnecessary references using Step 4's compatibility checks.
Use
ReferenceOutputAssembly="false"only for genuine build-order-only intent; it can reduce assembly-reference work, not the dependency wait itself.Consider a shared base library only if the resulting graph is better; a new common prerequisite can also lengthen the serial chain.
More nodes cannot parallelize a required dependency chain and can worsen memory- or I/O-bound workloads.
Step 7: Additional Quick Wins
Treat these as candidates selected by evidence, not a list to apply wholesale.
Separate Restore from Build
dotnet restore
if ($LASTEXITCODE -ne 0) { throw "Restore failed; do not use stale assets." }
dotnet build --no-restore -m
if ($LASTEXITCODE -ne 0) { throw "Build failed; do not test stale outputs." }
dotnet test --no-buildUse matching configuration/TFM/RID and restore-affecting properties in each stage.
--no-restore requires current assets; --no-build requires successfully built
matching test outputs. Keep restore-inclusive and build-only timings separate.
Skip Unnecessary Targets
For an explicitly agreed development scenario where these outputs/checks are optional:
dotnet build -p:GenerateDocumentationFile=false
dotnet build -p:RunAnalyzers=falseMeasure their actual cost first. Preserve required XML documentation in packages, required analyzer diagnostics in CI, and generator-produced code. Analyzer diagnostics covers conditional policy and global analyzer references. Do not silently call disabled validation behavior-preserving.
Use Project-Level Filtering
dotnet build .\src\MyApp\MyApp.csprojBuild the project and its dependencies when that is the intended inner loop. Solution filters can similarly select a supported subset. A smaller selection is a different scenario, not proof that the full solution became faster; retain the required integration and test coverage.
Binary Log for All Investigations
Start with an existing matching binlog or capture through binlog generation:
dotnet build -m "-bl:perf.binlog"Then use build performance diagnostics to identify the bottleneck before applying a change.
Optimization Decision Tree
Is the no-op build slow?
YES -> Classify evaluation, restore, copies, and executed work first.
Unexpected execution -> incremental-build.md.
Slow evaluation -> eval-performance.md.
NO / after classification:
Is the cold build slow?
Restore -> investigate feeds/cache/inputs; compare restore separately.
Compilation -> diagnose analyzers/generators and critical-path work.
Other targets -> inspect their recorded reasons and task costs.
Is the warm changed-input build slow?
Unexpected propagation -> incremental-build.md and dependency analysis.
Required serial work -> inspect critical path and parallelism above.
Are all scenarios healthy for the actual workload?
Stop. Do not change healthy configuration just to apply a tuning flag.Use the linked incremental, evaluation, and diagnostics references for those branches.
Validation and Results
Repeat the same scenario and input change with the same instrumentation. Verify required outputs, downstream consumers, diagnostics, and relevant tests or pack/publish behavior. For tracking changes, exercise input addition/change/removal and missing outputs as described in incremental builds. For copy changes, verify copy-mode semantics.
Report the command/environment, before/after median and spread, artifact paths, one causal change, correctness checks, and unmeasured scenarios. Stop or revert only your own experimental change if the benefit is within noise, the comparison cannot succeed, or correctness regresses.
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) or .NET (Microsoft).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download msbuild.zip