Problem
The Copy task checks File.Exists on source files before its retry loop. When the source doesn't exist, it immediately logs MSB3030 and returns false — no retry. The Retries/CopyRetryCount parameter only applies to IOException during the actual File.Copy call.
// src/Tasks/Copy.cs — outside the retry loop
{
Log.LogErrorWithCodeFromResources("Copy.SourceFileNotFound", ...);
return false; // MSB3030 — no retry
}
This causes flaky build failures in large parallel builds (/m) where shared dependency projects are rebuilt by multiple consumer projects. When a dependency's CopyFilesToOutputDirectory overwrites a DLL/PDB, consumer projects running _CopyFilesMarkedCopyLocal can see the source file as transiently missing.
Impact
In dotnet/aspnetcore, this is tracked as Known Build Error #62807 with ~25 hits per month. The only current mitigation is full build retry via Build Analysis, which wastes 30+ minutes per occurrence.
Reproduction
Any large parallel MSBuild solution where:
- Project A is a shared dependency (referenced by 100+ projects)
- Build runs with
/m (unlimited parallelism)
- Project A's
CopyFilesToOutputDirectory runs on one MSBuild node
- A consumer's
_CopyFilesMarkedCopyLocal runs on another node simultaneously
- The source file in
bin/ is briefly unavailable during the overwrite
Proposed Fix
Extend the retry loop in the Copy task to cover FileNotFoundException (or specifically the File.Exists pre-check), similar to how IOException is already retried during the copy operation. This would allow the Retries and RetryDelayMilliseconds parameters to protect against transiently-missing source files.
Workaround
We've added a BeforeTargets="_CopyFilesMarkedCopyLocal" gate in aspnetcore that waits for missing source files before the Copy task runs. This reduces the race window but doesn't eliminate the TOCTOU gap entirely.
Problem
The
Copytask checksFile.Existson source files before its retry loop. When the source doesn't exist, it immediately logs MSB3030 and returns false — no retry. TheRetries/CopyRetryCountparameter only applies toIOExceptionduring the actualFile.Copycall.This causes flaky build failures in large parallel builds (
/m) where shared dependency projects are rebuilt by multiple consumer projects. When a dependency'sCopyFilesToOutputDirectoryoverwrites a DLL/PDB, consumer projects running_CopyFilesMarkedCopyLocalcan see the source file as transiently missing.Impact
In
dotnet/aspnetcore, this is tracked as Known Build Error #62807 with ~25 hits per month. The only current mitigation is full build retry via Build Analysis, which wastes 30+ minutes per occurrence.Reproduction
Any large parallel MSBuild solution where:
/m(unlimited parallelism)CopyFilesToOutputDirectoryruns on one MSBuild node_CopyFilesMarkedCopyLocalruns on another node simultaneouslybin/is briefly unavailable during the overwriteProposed Fix
Extend the retry loop in the
Copytask to coverFileNotFoundException(or specifically theFile.Existspre-check), similar to howIOExceptionis already retried during the copy operation. This would allow theRetriesandRetryDelayMillisecondsparameters to protect against transiently-missing source files.Workaround
We've added a
BeforeTargets="_CopyFilesMarkedCopyLocal"gate in aspnetcore that waits for missing source files before the Copy task runs. This reduces the race window but doesn't eliminate the TOCTOU gap entirely.