Symptom
Weaving fails with PS0264 when the compiled project references a package at a higher version than the one PostSharp's own compiler host depends on:
error PS0264: Could not load file or assembly
'~/.nuget/packages/system.security.cryptography.protecteddata/10.0.0/lib/net10.0/System.Security.Cryptography.ProtectedData.dll'.
The located assembly's manifest definition does not match the assembly reference. (0x80131040)
Reported by a customer on PostSharp 2026.0.10, .NET 10 host, project referencing System.Security.Cryptography.ProtectedData 10.0.0. Any package shared between the compiled project's graph and the compiler's own graph can trigger it.
The failure is not first-build; it appears on a later build and is then deterministic until obj/ is cleaned. A CI that always starts from a clean obj/ never sees it, which makes it look platform-specific when it is not.
Root cause
ProjectDepsBuilder.Build computes upgradedHostRuntimeDependencies — the set of compiler host runtime assemblies superseded by a higher version from the compiled project's graph. CompilerRuntimeDirectoryBuilder.Build uses that set to avoid copying the compiler's own lower-versioned copy into the project's intermediate directory (Core/PostSharp.MSBuild/NetCore/CompilerRuntimeDirectoryBuilder.cs, lines 64-68).
The set is computed only inside the deps.json regeneration branch (Core/PostSharp.MSBuild/NetCore/ProjectDepsBuilder.cs, lines 346-367). When the cached deps.json is up to date, the method returns early at lines 185-192 with the out parameter left at its initial null (line 141). The exclusion is therefore silently skipped on every build that reuses the cached deps.json.
Sequence:
- Build 1 (after restore) regenerates the deps.json, computes the set, correctly skips the file.
- Build 2 reuses the cached deps.json, gets
null, and copies System.Security.Cryptography.ProtectedData.dll (AssemblyVersion 4.0.2.0, from our NuGet.Packaging -> NuGet.Configuration chain) into the intermediate directory.
- Nothing ever removes it —
CompilerRuntimeDirectoryBuilder enumerates the tool directory and skips entries; it never sweeps the destination.
- The compiler host is executed from that same intermediate directory (
BuildClient.cs line 891 sets finalToolPath there), so the stale assembly sits next to the host as an app-directory assembly.
- The weaver's later
AssemblyLoadContext.Default.LoadFromAssemblyPath for the project's 10.0.0.0 file fails with FUSION_E_REF_DEF_MISMATCH (0x80131040).
Note that the PS0265 log shows "is not yet loaded in the AppDomain" rather than "An assembly with the same name ... is loaded in the application domain, but it's identity doesn't match" (RuntimeAssemblyLocator.cs lines 224-233). The conflicting assembly is therefore never actually loaded — the conflict is at the host resolution level, not between two loaded assemblies.
What is verified vs. inferred
Verified:
- Steps 1-2 are reproduced by an automated test (see below).
System.Security.Cryptography.ProtectedData.dll at AssemblyVersion 4.0.2.0 does ship in the compiler tools directory; confirmed against a local build output.
- Steps 3-4 are established by code inspection.
Inferred, not directly observed:
- Step 5 — that the app-directory copy is what wins host assembly resolution over the deps.json entry pointing at the 10.0.0 package path. This is not reproduced here. It is however exactly the rationale recorded in the existing mitigation's own comment (
ProjectDepsBuilder.cs lines 346-348: "these should not be included in the compiler runtime directory because .NET 8.0+ runtimes tend to load them even when they should be replaced by higher versions"), so the mechanism is established by prior art in this codebase. Confirming it against a live repro would be worthwhile before closing.
Reproduction
Tests/Core/PostSharp.Compiler.Client.Test/ProjectDepsBuilderTests.cs, two tests over one fixture:
UpgradedHostRuntimeDependencies_AreReported_WhenDepsFileIsGenerated — passes, pinning the intended behaviour on the fresh path.
UpgradedHostRuntimeDependencies_AreReported_WhenDepsFileIsCached — fails:
The set of upgraded host runtime dependencies was not computed when the deps.json was taken from the cache.
Expected: not null
But was: null
The fixture is self-contained (temporary directory, synthetic package folder, synthetic host deps.json and project.assets.json); it needs neither network nor the local NuGet cache.
Proposed fix
- Compute
upgradedHostRuntimeDependencies before the cache short-circuit. It only needs the host deps.json and the merged dependency set, both cheap to obtain; alternatively persist it alongside the cached deps.json.
- Remove a superseded copy already present in the intermediate directory, so machines already in the broken state recover without manual intervention.
Workaround
Delete the stale assembly from the intermediate directory and force one regeneration:
rm obj/<Configuration>/<TargetFramework>/System.Security.Cryptography.ProtectedData.dll
POSTSHARPHOST_DISABLE_CACHE=true dotnet build
The environment variable forces the deps.json to be regenerated, which recomputes the exclusion set. It must be kept set (or the deletion scripted as a pre-build step) until a fixed build is available, because the very next cached build re-plants the file.
Symptom
Weaving fails with
PS0264when the compiled project references a package at a higher version than the one PostSharp's own compiler host depends on:Reported by a customer on PostSharp 2026.0.10, .NET 10 host, project referencing
System.Security.Cryptography.ProtectedData10.0.0. Any package shared between the compiled project's graph and the compiler's own graph can trigger it.The failure is not first-build; it appears on a later build and is then deterministic until
obj/is cleaned. A CI that always starts from a cleanobj/never sees it, which makes it look platform-specific when it is not.Root cause
ProjectDepsBuilder.BuildcomputesupgradedHostRuntimeDependencies— the set of compiler host runtime assemblies superseded by a higher version from the compiled project's graph.CompilerRuntimeDirectoryBuilder.Builduses that set to avoid copying the compiler's own lower-versioned copy into the project's intermediate directory (Core/PostSharp.MSBuild/NetCore/CompilerRuntimeDirectoryBuilder.cs, lines 64-68).The set is computed only inside the deps.json regeneration branch (
Core/PostSharp.MSBuild/NetCore/ProjectDepsBuilder.cs, lines 346-367). When the cached deps.json is up to date, the method returns early at lines 185-192 with the out parameter left at its initialnull(line 141). The exclusion is therefore silently skipped on every build that reuses the cached deps.json.Sequence:
null, and copiesSystem.Security.Cryptography.ProtectedData.dll(AssemblyVersion 4.0.2.0, from ourNuGet.Packaging->NuGet.Configurationchain) into the intermediate directory.CompilerRuntimeDirectoryBuilderenumerates the tool directory and skips entries; it never sweeps the destination.BuildClient.csline 891 setsfinalToolPaththere), so the stale assembly sits next to the host as an app-directory assembly.AssemblyLoadContext.Default.LoadFromAssemblyPathfor the project's 10.0.0.0 file fails withFUSION_E_REF_DEF_MISMATCH(0x80131040).Note that the PS0265 log shows "is not yet loaded in the AppDomain" rather than "An assembly with the same name ... is loaded in the application domain, but it's identity doesn't match" (
RuntimeAssemblyLocator.cslines 224-233). The conflicting assembly is therefore never actually loaded — the conflict is at the host resolution level, not between two loaded assemblies.What is verified vs. inferred
Verified:
System.Security.Cryptography.ProtectedData.dllat AssemblyVersion 4.0.2.0 does ship in the compiler tools directory; confirmed against a local build output.Inferred, not directly observed:
ProjectDepsBuilder.cslines 346-348: "these should not be included in the compiler runtime directory because .NET 8.0+ runtimes tend to load them even when they should be replaced by higher versions"), so the mechanism is established by prior art in this codebase. Confirming it against a live repro would be worthwhile before closing.Reproduction
Tests/Core/PostSharp.Compiler.Client.Test/ProjectDepsBuilderTests.cs, two tests over one fixture:UpgradedHostRuntimeDependencies_AreReported_WhenDepsFileIsGenerated— passes, pinning the intended behaviour on the fresh path.UpgradedHostRuntimeDependencies_AreReported_WhenDepsFileIsCached— fails:The fixture is self-contained (temporary directory, synthetic package folder, synthetic host deps.json and
project.assets.json); it needs neither network nor the local NuGet cache.Proposed fix
upgradedHostRuntimeDependenciesbefore the cache short-circuit. It only needs the host deps.json and the merged dependency set, both cheap to obtain; alternatively persist it alongside the cached deps.json.Workaround
Delete the stale assembly from the intermediate directory and force one regeneration:
The environment variable forces the deps.json to be regenerated, which recomputes the exclusion set. It must be kept set (or the deletion scripted as a pre-build step) until a fixed build is available, because the very next cached build re-plants the file.