the symptom, in your words

"A git worktree can't build — the .csproj files are gitignored"

✓ verified on 26.07.15a
lane Tooling & environmentposted

▸ SYMPTOM

You git worktree add a second checkout to run a headless build, and:

snippet
dotnet build Code\MyProject.csproj
MSB1009: Project file does not exist.

The exact same command works in the main working tree. The worktree's Code/ and Editor/ folders are there, but there is no .csproj in them at all.

▸ CAUSE

s&box generates the .csproj files from the editor and gitignores them (*.csproj is in .gitignore). They are never committed, so a fresh worktree — which only contains tracked files — has no project file for dotnet build to build. The main tree works only because the editor already generated its csproj there.

▸ FIX

Copy the two project files from the main working tree into the worktree before building:

snippet
cp main-tree/Code/*.csproj          worktree/Code/
cp main-tree/Editor/*.editor.csproj worktree/Editor/

These are boilerplate SDK-style projects whose references are relative to the s&box install (e.g. ../../../../../../../Program Files (x86)/Steam/steamapps/common/sbox/...). The copy resolves correctly only if the worktree sits at the same path depth as the main tree — if both are the same number of segments under your dev root, the relative reference count matches. Never commit the copied .csproj (still gitignored — verify with git status after copying).

If the worktree is at a different (deeper) depth

A scratch worktree nested deeper than the main tree makes the relative ../…/Steam/... refs resolve to the wrong root, and the copied csproj can't find the s&box DLLs. Instead author a worktree-local .csproj with absolute paths (depth-independent) for the Reference / Analyzer / ProjectReference entries, pointing at your s&box install (default C:/Program Files (x86)/Steam/steamapps/common/sbox/...).

Also redirect <OutputPath> and <DocumentationFile> to an isolated in-worktree folder — the stock csproj writes to the shared sbox/.vs/output/, which the live editor uses, and a verify build must not clobber it.

Do not point <BaseIntermediateOutputPath> at a custom folder: it warns MSB3539, and stale generated *.AssemblyInfo.cs / AssemblyAttributes.cs left under it get globbed into the compile, producing a wall of CS0579 duplicate-attribute errors. Leave obj/ at its default (the SDK auto-excludes it from the compile glob) and rm -rf any stray intermediate/output dir before rebuilding.

The --no-incremental flake

With an isolated custom <OutputPath>, a second build run with --no-incremental can fail:

snippet
MSB3030: Could not copy … deps.json / staticwebassets.endpoints.json … not found

This is a stale-state race, not a code error: --no-incremental clears the output, then the copy-outputs step races the just-cleared isolated dir. The first build (or a plain dotnet build without --no-incremental) succeeds. Fix: rm -rf the custom output and obj/, then run dotnet build without --no-incremental. For a deeper worktree with a redirected OutputPath, prefer a clean-plus-plain build over --no-incremental.

▸ WHY IT WORKS

Copying (or authoring) the csproj gives dotnet build the project file that s&box normally regenerates on the editor side but never commits. Matching path depth keeps the relative install references valid; absolute references remove the depth dependency entirely. Isolating the output directory keeps the verify build from overwriting the editor's shared output, and staying on the default obj/ with a plain build avoids both the duplicate-attribute glob and the --no-incremental copy race.

Verified on engine 26.07.15a: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?
changelog
  • Published

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord