dotnet / dotnet/msbuild

CLR4 TaskHost does not inherit parent host assembly binding policy

Open
#14,838 3 comments 0 reactions 0 assignees View on GitHub
Area: Multithreaded Area: TaskHost Priority:3 triaged Visual Studio
Dominant language
C#
Stars
5.5k
Forks
1.5k
Avg merge
1d 8h
Merged PRs (30d)
141

Description

## Summary

CLR4 TaskHost execution does not reproduce the parent host's effective assembly-binding policy for an ordinary task. A task that succeeds in-process through a binding redirect can therefore fail in TaskHost with `MSB4018` / `FileNotFoundException`.

This was found through an **artificial compatibility experiment** using the internal `MSBUILDFORCEALLTASKSOUTOFPROC=1` escape hatch. It is not a report of a known production build break, and forcing every task into TaskHost is not a supported normal build configuration.

## Minimal repro

https://gist.github.com/JanProvaznik/096bb6c1a96a442313ed4ac8eb7927f9

Run:

```powershell
.\build-and-run.ps1 -MSBuildBin C:\path\to\net472\MSBuild\Current\Bin\amd64
```

The repro creates:

- A strong-named `Repro.Dependency` version `1.0.0.0`.
- A task compiled against dependency version 1.
- A strong-named `Repro.Dependency` version `2.0.0.0`, which is the only version deployed beside the task.
- A custom MSBuild host with a binding redirect from versions 0–2 to version 2.

It then builds the same project twice:

1. Normally, with the task executing inside the custom host.
2. With `MSBUILDFORCEALLTASKSOUTOFPROC=1`, which routes the task through an external CLR4 TaskHost.

## Actual behavior

The in-process invocation succeeds:

```text
Building in the custom host process.
Loaded Repro.Dependency 2.0.0.0
Build succeeded.
```

The TaskHost invocation fails:

```text
Building with every task forced into TaskHost.
error MSB4018: The "ReportDependencyVersion" task failed unexpectedly.
error MSB4018: System.IO.FileNotFoundException:
Could not load file or assembly
'Repro.Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...'
```

Reproduced against MSBuild `18.10.0.35201`, source commit `65f75f9249a01a3ec8e5220f5173eba3b5db7bc5`.

## Expected behavior

TaskHost should apply compatible managed assembly-binding policy so that a task which succeeds in its parent host does not fail solely because execution moved across the TaskHost boundary.

## Mechanism

`TaskHostConfiguration` transports the parent's `AppDomainSetup` configuration bytes. However, those settings are applied only when `TaskLoader` creates a separate AppDomain for a task marked with `[LoadInSeparateAppDomain]`.

An ordinary task executes directly in the external TaskHost process's root AppDomain. That domain was created using the TaskHost executable's configuration, not the custom parent host's binding policy. Consequently:

- The parent host redirects dependency version 1 to the deployed version 2.
- TaskHost requests the exact compiled identity, version 1.
- Version 2 is physically present beside the task, but strong-name binding correctly rejects it without policy redirecting version 1 to version 2.

Adding a resolver that loads any adjacent assembly with the same filename would not be a safe general fix because it would bypass strong-name version policy.

## Possible direction

For CLR4 TaskHost, execute tasks inside an AppDomain created from the transported parent `AppDomainSetup`, with the task loading and execution occurring entirely inside that domain. Reuse would need to be scoped by an effective configuration fingerprint, or the domain could be unloaded per task.

The compatibility and performance implications need evaluation because changing binding behavior could affect existing tasks that already use TaskHost explicitly.

Contributor guide

No contributing guide indexed for this repository

Research direction

Trace TaskHostConfiguration's transported AppDomainSetup and TaskLoader's separate-AppDomain path, then reproduce the discrepancy with the linked minimal repro and MSBUILDFORCEALLTASKSOUTOFPROC=1. Done means ordinary CLR4 TaskHost execution honors the parent's compatible binding policy without unsafe filename-based resolution, with compatibility and performance implications evaluated.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
build-system, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.