Quick answer: The export pipeline rebuilds your assemblies in ExportRelease configuration, but Visual Studio, Rider, or a stale .godot/mono/temp cache holds the PDB or DLL open and the build cannot overwrite it. Close every IDE and debugger, run dotnet clean && dotnet build -c ExportRelease, delete .godot/mono/temp, then re-export — or use godot --headless --export-release from CI where nothing holds locks.
Here is how to fix Godot C# project exports that fail with errors like Could not load file or assembly, PDB is locked, or Access to the path is denied. You hit Export, the build dialog spins for a moment, and then a wall of red MSBuild output appears: a file in .mono/temp/bin/ is locked, or an assembly cannot be loaded, or NuGet package files are in use. Restarting Godot fixes it sometimes. Closing your IDE fixes it more often. The fundamental problem is that the export pipeline runs a parallel build of the same files your editor and IDE already opened.
The Symptom
You trigger Export from the Godot editor and the build phase fails. Common errors:
The process cannot access the file because it is being used by another process. Specifically references a .pdb, .dll, or NuGet package file under .mono/temp/ or bin/. The export attempted to overwrite a file that something else has open for writing.
Could not load file or assembly ‘ProjectName’ or one of its dependencies. The export rebuilt the main assembly but a referenced assembly is still the old version, and the type signatures no longer match. Common after switching branches or updating a NuGet package.
MSB3026: Could not copy “obj/ExportRelease/.../ProjectName.dll” because it was being used by another process. Same root cause — a debugger or analyzer has the file open. The MSBuild output usually names the locking process if you scroll up.
Export succeeds in headless mode but fails in the editor. Running godot --headless --export-release from a fresh terminal works, but the editor’s Export dialog fails. This is your strongest signal that the editor or an attached IDE is the locker.
What Causes This
IDE has the PDB open for debugging symbols. Visual Studio and Rider open .pdb files when they index the project for IntelliSense, and again when you start a debug session. Symbol files are mapped read-only most of the time but become read-write the moment you set a breakpoint or run an analyzer. The export pipeline tries to overwrite them and fails.
dotnet watch or background analyzers. If you ran dotnet watch run at any point in this session, a background dotnet process may still be holding the build outputs. Same for OmniSharp, Roslyn analyzers, and any source generator that did not exit cleanly.
.godot/mono/temp cache out of sync. Godot 4.x stores intermediate build outputs under .godot/mono/temp/. After branch switches, NuGet upgrades, or Godot version bumps, the cache can hold stale assemblies that get loaded ahead of the freshly built ones during the export. Type-load errors result.
NuGet package lock files. The NuGet restore step locks packages under ~/.nuget/packages/ while writing. If the export starts a restore concurrent with another build, both attempt to write the same files and one fails.
Wrong build configuration. The editor builds Debug by default, but export uses ExportRelease. If your project depends on configuration-conditional code or a missing reference only in Release mode, export fails with errors that never surface during normal play.
The Fix
Step 1: Close every IDE and debugger first. Quit Visual Studio, Rider, VS Code with C# extensions, and any terminal running dotnet watch. On Windows, check Task Manager for stragglers (devenv, jetbrains.rider, OmniSharp, dotnet with no obvious owner). On macOS/Linux, run ps aux | grep -i "dotnet\|rider\|omnisharp" and kill anything that should not be there.
Step 2: Clean and rebuild from the command line. Run a full clean and ExportRelease build outside Godot to surface any real errors and prime the cache:
# From the project root
cd /path/to/project
dotnet clean
dotnet build -c ExportRelease
# If that succeeds, the assemblies are fresh on disk
# Now retry the export from Godot
If the command-line build also fails, you have a real compile error and the export was never going to succeed. Fix the build first, then export.
Step 3: Nuke the editor cache. If the command-line build succeeds but the export still fails, the Godot editor is loading stale assemblies from its own cache. Close Godot completely, then delete the cache directory:
# Close Godot first, then:
rm -rf .godot/mono/temp
rm -rf .godot/mono/assemblies
# On Windows PowerShell:
# Remove-Item -Recurse -Force .godot\mono\temp
# Remove-Item -Recurse -Force .godot\mono\assemblies
Re-open Godot. The editor will rebuild the cache from scratch on first load (this takes 30-60 seconds for a medium project). Then re-attempt the export.
Headless Export From Scripts
For CI pipelines or local automation, skip the editor entirely. Headless export does not load the inspector, does not start any analyzers, and does not hold file locks beyond the build itself:
# Headless export script — runs from CI or a local terminal
using Godot;
using System;
// Step 1: Import the project so .godot/ is populated
// godot --headless --quit-after 200
// Step 2: Build assemblies in ExportRelease
// dotnet build -c ExportRelease
// Step 3: Export
// godot --headless --export-release "Windows Desktop" \
// build/MyGame.exe
// Bash example wrapping all three:
// #!/usr/bin/env bash
// set -euo pipefail
// godot --headless --quit-after 200 || true
// dotnet clean
// dotnet build -c ExportRelease
// godot --headless --export-release "Linux/X11" build/MyGame.x86_64
The --quit-after 200 flag gives Godot 200 frames to finish importing the project before quitting. Without it, the headless instance never returns. || true on the import step is because import sometimes exits with a non-zero code even on success.
Diagnosing Which Process Holds The Lock
If you cannot identify which process is locking the file, use the system tools:
Windows. Sysinternals handle.exe: handle.exe -nobanner ProjectName.pdb. The output names every process holding a handle to that file.
macOS/Linux. lsof | grep ProjectName.pdb shows the same information. fuser -v ProjectName.pdb on Linux is more concise.
Once you know the process, you know what to close. The most common culprit is a Rider instance you forgot was running on a different desktop, or an OmniSharp process VSCode left behind when it crashed.
“Two compilers cannot share one file. The export pipeline does not know about your IDE, and your IDE does not know about the export. You have to be the referee.”
Related Issues
If exports succeed but the runtime crashes with a missing assembly, see C# Runtime TypeLoadException. For NuGet restore failures specifically, check NuGet Restore Fails on Godot Export.
Close the IDE, clean the cache, build with -c ExportRelease, then export — in that order.