System.IO.IOException: The process cannot access the file '...' because it is being used by another process from dotnet build, failing in under a second with no retries, means a second build of the same project was running at the same moment. Both builds write the same obj and bin files, and Windows refuses a file to one build while the other has it open.

Let the other build finish, or stop it by its process id, then run one build. If the message instead says MSB3027 and names your app after ten one-second retries, the culprit is your running app, not a build: that case has its own post.

The error

...\Microsoft.NET.Sdk.targets(404,5): error MSB4018: The "GenerateRuntimeConfigurationFiles" task failed unexpectedly. [...\ConLab\ConLab.csproj]
...\Microsoft.NET.Sdk.targets(404,5): error MSB4018: System.IO.IOException: The process cannot access the file '...\ConLab\bin\Debug\net10.0\ConLab.runtimeconfig.json' because it is being used by another process. [...\ConLab\ConLab.csproj]
...\Microsoft.NET.Sdk.targets(404,5): error MSB4018:    at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options) [...\ConLab\ConLab.csproj]

Paths are shortened; nothing else changed. The same race produced two other shapes, depending on which file the two builds reached together:

CSC : error CS2012: Cannot open '...\ConLab\obj\Debug\net10.0\ConLab.dll' for writing -- The process cannot access the file '...\ConLab\obj\Debug\net10.0\ConLab.dll' because it is being used by another process. [...\ConLab\ConLab.csproj]
...\Microsoft.NET.Sdk.StaticWebAssets.targets(706,5): error : System.IO.IOException: The process cannot access the file '...\LockLab\obj\Debug\net10.0\rpswa.dswa.cache.json' because it is being used by another process. [...\LockLab\LockLab.csproj]

Why it happens

Each dotnet build is its own process, and nothing makes a second one wait for the first. Both run the same targets against the same folders: the compiler writes obj\Debug\net10.0\ConLab.dll, an SDK task writes ConLab.runtimeconfig.json into bin, and in a web project the static web assets task reads and rewrites its cache file in obj. When one build holds a file open and the other tries to open it, Windows refuses with a sharing violation, which .NET reports as IOException. Unlike the copy step, which retries ten times before MSB3027, these tasks do not retry, so the losing build above stopped after 0.84 seconds while the other one succeeded.

The race is timing-dependent: in the tests below it failed in some rounds and passed in others, which makes it look random. The usual sources of a second build are a second terminal, an IDE building while you build from the command line, and commands that build before they do their real work, such as dotnet run, dotnet test and dotnet ef.

The fix

PS> Get-CimInstance Win32_Process -Filter "Name = 'dotnet.exe'" | Where-Object CommandLine -match ' build' | Select-Object ProcessId, CommandLine

ProcessId CommandLine
--------- -----------
     2112 "C:\Program Files\dotnet\dotnet.exe" build --no-incremental

PS> Stop-Process -Id 2112
PS> dotnet build
  Determining projects to restore...
  All projects are up-to-date for restore.
  LockLab -> ...\LockLab\bin\Debug\net10.0\LockLab.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

The query lists every dotnet build on the machine with its id; stop only the one you started for this project. Waiting for it to finish is the gentler option and works just as well: the same two builds that collided both succeeded when run one after the other. Then keep to one build at a time per project, and pass --no-build to dotnet run or dotnet test when a build has just finished elsewhere.

How it was reproduced

Two throwaway projects, dotnet new console and dotnet new web, no packages. Two dotnet build commands were started at the same moment from separate shells, both with --no-incremental and as plain builds after a code edit. The web project failed in five of eight rounds (static web assets cache four times, CS2012 once), the console project in two of four (CS2012, runtimeconfig.json). With the app running instead, the build printed only MSB3026, MSB3027 and MSB3021. A manual build during a dotnet watch restart did not collide in four tries. .NET SDK 10.0.402, MSBuild 18.9.15, Windows 11.

Frequently asked

Why does dotnet build say the process cannot access the file because it is being used by another process?
Another process has that file open. If the message is MSB3027 and names your app, the app is still running. If it is a raw System.IO.IOException or CS2012 on a file in obj, or a json file in bin, and the build fails within a second, a second build of the same project was running at the same time. Let it finish or stop it, then build once.
What does CS2012 Cannot open for writing mean in dotnet build?
The compiler could not write the assembly in the obj folder because another process had it open. In this test the other process was a second dotnet build of the same project started at the same moment. Running a single build afterwards succeeded.
Is it safe to stop a running dotnet build process?
In this test, stopping the other build with Stop-Process and its process id, then running dotnet build again, gave a clean build. Waiting for the other build to finish is the gentler option when it is close to done.

More decoded errors in the Fixes category.