Skip to content
Open
8 changes: 7 additions & 1 deletion src/Tasks.UnitTests/Copy_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,13 @@ public void DoNotNormallyCopyOverReadOnlyFile(bool isUseHardLinks, bool isUseSym
string destinationContent = File.ReadAllText(destination);
Assert.Equal("This is a destination file.", destinationContent);

((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // did not do retries as it was r/o
if (NativeMethodsShared.IsWindows)
{
// On Windows, ERROR_ACCESS_DENIED is not retried (it's a real ACL or r/o bit issue).
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026");
}
// On non-Windows, access denied can be a transient lock (e.g. macOS CoW filesystem),
// so we retry; retries will ultimately fail for a genuinely read-only file.
}
Comment on lines +755 to 764
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On non-Windows this test no longer asserts anything about the new retry behavior (it only documents it in a comment). To keep the behavior change covered, add an assertion for the non-Windows path (e.g., that the retry warning code MSB3026 appears) or add a dedicated test that simulates an UnauthorizedAccessException(ERROR_ACCESS_DENIED) and verifies the Copy task enters the retry loop on non-Windows.

Copilot generated this review using guidance from repository custom instructions.
finally
{
Expand Down
5 changes: 4 additions & 1 deletion src/Tasks/Copy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,10 @@ private bool DoCopyWithRetries(FileState sourceFileState, FileState destinationF
// to a failure to reset the readonly bit properly, in which case retrying will succeed. This seems to be
// a pretty edge scenario, but since some of our internal builds appear to be hitting it, provide a secret
// environment variable to allow overriding the default behavior and forcing retries in this circumstance as well.
if (!s_alwaysRetryCopy)
// On non-Windows platforms (e.g. macOS copy-on-write filesystems), access denied can also indicate a
// transient lock conflict (e.g. EACCES from a concurrent clonefile/flock), so we only skip retries for
// access denied on Windows.
if (!s_alwaysRetryCopy && NativeMethodsShared.IsWindows)
{
throw;
}
Expand Down