Skip to content

fix: resolve MSB3491 race condition in WriteLaunchers parallel builds#199

Open
BenjaminMichaelis wants to merge 1 commit into
mainfrom
agents/fix-msb3491-race-condition
Open

fix: resolve MSB3491 race condition in WriteLaunchers parallel builds#199
BenjaminMichaelis wants to merge 1 commit into
mainfrom
agents/fix-msb3491-race-condition

Conversation

@BenjaminMichaelis
Copy link
Copy Markdown
Member

@BenjaminMichaelis BenjaminMichaelis commented May 14, 2026

Problem

WriteLaunchers wrote build variables to a hardcoded global temp path:

%TEMP%\IntelliTect.MultiTool.BuildVariables.tmp

When a solution has multiple projects referencing this package, dotnet build builds them in parallel. All projects resolved to the same absolute file path, and WriteLinesToFile with Overwrite="true" is not atomic (delete-then-create). Two parallel project builds racing on that path caused:

error MSB3491: Could not write lines to file "...\IntelliTect.MultiTool.BuildVariables.tmp".
Cannot create a file when that file already exists.

There was also a correctness bug: even when writes didn't collide, the winning writer was non-deterministic — the file could end up containing variables from any project in the solution, not the one that would consume it at runtime.

Fix

Write the build variables file to $(OutDir) — each project's output directory — which is already unique per project and per TFM. Update the C# reader to look in AppContext.BaseDirectory instead of Path.GetTempPath(), since AppContext.BaseDirectory resolves to the same output directory at runtime.

Changes

IntelliTect.Multitool/Build/IntelliTect.Multitool.targets

  • Remove the TempStagingPath property (was the source of the shared path)
  • Collapse two conditional WriteLinesToFile calls into one unconditional write to $(OutDir)IntelliTect.MultiTool.BuildVariables.tmp

IntelliTect.Multitool/RepositoryPaths.cs

  • Path.GetTempPath()AppContext.BaseDirectory in BuildVariables initializer

IntelliTect.Multitool.AotTest/Program.cs

  • Update stale comment to reflect the new file location

Why $(OutDir) + AppContext.BaseDirectory

Property Value
Parallel-safe ✅ Each project has a unique output dir
LUT-compatible ✅ VS LUT shadow-copies the entire output dir; AppContext.BaseDirectory follows
AOT-safe AppContext.BaseDirectory works in single-file/AOT (unlike Assembly.Location)
Cross-platform $(OutDir) always ends with a path separator; valid on Windows and Linux

Write build variables to \ (per-project output directory)
instead of a shared global temp path. Multiple projects building in
parallel each get their own unique output directory, eliminating the
delete-then-create race on the shared temp file.

Update the RepositoryPaths.BuildVariables reader to use
AppContext.BaseDirectory instead of Path.GetTempPath(), since the
.tmp file now lives alongside the built assembly. AppContext.BaseDirectory
is AOT-safe and correctly resolves in Live Unit Testing scenarios.

Remove the now-unnecessary TempStagingPath property and collapse the
two conditional WriteLinesToFile calls into one unconditional write.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a parallel-build race (MSB3491) and a non-determinism bug by moving the “build variables” file from a shared global temp path to each project’s output directory, and updating runtime lookup accordingly.

Changes:

  • Write IntelliTect.MultiTool.BuildVariables.tmp to $(OutDir) (per-project/per-TFM) instead of %TEMP%.
  • Update RepositoryPaths.BuildVariables to read from AppContext.BaseDirectory (matching the output dir at runtime).
  • Refresh an AOT test comment to reflect the new file location.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
IntelliTect.Multitool/Build/IntelliTect.Multitool.targets Writes build variables file into $(OutDir) to avoid parallel build collisions and cross-project contamination.
IntelliTect.Multitool/RepositoryPaths.cs Reads build variables from AppContext.BaseDirectory so runtime resolves the same per-project output directory file.
IntelliTect.Multitool.AotTest/Program.cs Updates comment describing why RepositoryPaths is excluded from AOT test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@BenjaminMichaelis BenjaminMichaelis requested a review from Keboo May 14, 2026 16:11
@BenjaminMichaelis BenjaminMichaelis self-assigned this May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants