error MSB3021: Unable to copy file ... The process cannot access the file ... because it is being used by
another process. — the DLL or exe you are building is still loaded by a running program, and on Windows
a loaded binary cannot be overwritten.
That running program is almost always your own: the server from the last dotnet run, a desktop app
you launched to test, or a test host that has not exited yet. MSBuild even names it in the message. Stop that
process and build again. If the project you are building is a test project or a client, remember it rebuilds
every project it references — the lock can belong to a dependency's process, not the project you named.
The error
Reproduced with a throwaway console app so the text is exact (paths shortened); the shape is the same for any DLL:
warning MSB3026: Could not copy "obj\Debug\net10.0\apphost.exe" to "bin\Debug\net10.0\ArgsDemo.exe".
Beginning retry 1 in 1000ms. The process cannot access the file '...\ArgsDemo.exe' because it is
being used by another process. The file is locked by: "ArgsDemo (46200)"
/* ... retries 2 to 10, one second apart ... */
error MSB3027: Could not copy "obj\Debug\net10.0\apphost.exe" to "bin\Debug\net10.0\ArgsDemo.exe".
Exceeded retry count of 10. Failed. The file is locked by: "ArgsDemo (46200)"
error MSB3021: Unable to copy file "obj\Debug\net10.0\apphost.exe" to "bin\Debug\net10.0\ArgsDemo.exe".
The process cannot access the file '...\ArgsDemo.exe' because it is being used by another process.
Why it happens
Windows maps a running executable and its loaded DLLs into memory and keeps the files open. Overwriting an open binary is refused at the file-system level, so MSBuild's copy-to-output step fails. It retries ten times a second apart (each attempt is an MSB3026 warning), then gives up with MSB3027 and reports the underlying copy failure as MSB3021. Linux and macOS let you replace a running binary, which is why this is a Windows-shaped surprise.
The subtle version: you did not run the project that fails. ClinicLive's test project references the server
project, so dotnet build tests/ClinicLive.Tests rebuilds ClinicLive.dll first — and
that file is locked by the server you left running in another terminal.
The fix
# the message names the process and its id — stop that one, not every dotnet.exe
Stop-Process -Id 46200 # PowerShell
taskkill /PID 46200 /F # cmd
# no id in the message? find it by name
Get-Process ClinicLive*
Then build again. The habit that stops this recurring is simple: stop before build, and remember that "stop"
includes every app that has one of the project's outputs loaded — the web host, the Windows desktop build and the
test host all count. For a server you edit constantly, dotnet watch run stops the process itself
before each rebuild, so the lock never bites.
Where it bit us
Season three, Part 3 (tag pocket-03 in
the repo): the build failed on DLL locks because the
web host and the Windows app were both still running from the previous screenshot pass. It came back in
Part 7, in its subtle form: the test build failed twice on a locked
ClinicLive.dll while only the server was running. Season one had already learned the rule; the lesson
of the second bite was that stop-before-build applies to dependencies too.
Frequently asked
- How do I find which process has my DLL locked?
- Recent MSBuild versions print it in the MSB3026 and MSB3027 messages as The file is locked by, with the process name and id. Otherwise use Get-Process in PowerShell or Resource Monitor's CPU tab, Associated Handles, and search for the file name.
- Why does building my test project fail when only the web server is running?
- A project reference makes the test build rebuild the referenced project first. Its output DLL is loaded by the running server, so the copy step fails even though you never asked to build the server. Stop the server, then build the tests.
- Does MSB3021 happen on Linux or macOS?
- Rarely for this reason. Those systems allow a running binary to be replaced on disk, so the copy succeeds. The error is a Windows file-sharing rule, which is why CI on Ubuntu never sees it and your Windows machine does.
More decoded errors in the Fixes category; the build this came from starts at From Prompt to Production, Part 1.