CSProj Duplicate PackageReference Trap

Definition

When git auto-merges or rebases produce a .csproj with two or more <PackageReference> lines for the same package, MSBuild builds opaquely without flagging the dup. Downstream behaviour is undefined: usually the first reference wins, sometimes a NuGet downgrade warning fires, sometimes a transitive dependency resolves to an unexpected version. Restore appears to succeed; build fails far away from the actual cause.

How it shows up

Most-frequent offender in CRMAPIGenerator: Newtonsoft.Json in Templates/Templates.csproj. Runs on Builder agent fail with errors like:

  • CS0246: The type or namespace name 'JsonConvert' could not be found
  • MSB3277: Found conflicts between different versions of "Newtonsoft.Json"
  • Or worse: build succeeds but the runtime throws MissingMethodException against a method that exists in a different version

How to fix

# Find duplicates in any csproj:
grep -nE 'PackageReference Include="([^"]+)"' Templates/Templates.csproj | sort -u | wc -l
# Compare to total PackageReference lines:
grep -cE 'PackageReference Include="' Templates/Templates.csproj
# If counts differ, you have duplicates.

Manual fix: open the csproj, dedupe the <PackageReference> lines, keep the highest version, run dotnet restore && dotnet build.

Why agents miss it

Builder agent gets a dotnet build failure stderr, doesn’t grep for duplicate refs in the csproj, and tries to “fix” the surface error (e.g. adds an <Import> it doesn’t need). This compounds the problem.

Prescription for the Builder agent

When dotnet build fails AND the diff for this run touched any .csproj, before any other action:

  1. grep -E 'PackageReference Include="([^"]+)"' <touched-csproj> and check for duplicates
  2. If duplicates exist, dedupe (keep highest version) and retry
  3. If still failing, then proceed to other diagnostics
  • [[NuGet-v3-vs-v6-Resolution]] — adjacent: not about duplicates but about transitive resolution
  • `CRMAPIGenerator-Repository — the project where this trap lives