Report actual launch path in MSB4216 for Runtime="NET" task host#13889
Merged
ViktorHofer merged 3 commits intoMay 29, 2026
Merged
Conversation
When a .NET task host launch fails, LogErrorUnableToCreateTaskHost always formatted the path as MSBuild.exe (the apphost) regardless of whether ResolveAppHostOrFallback actually launched the apphost or fell back to dotnet.exe MSBuild.dll. SDK 10.0.x ships MSBuild.dll only — the apphost first ships in 10.0.300 — so users on those SDKs see an MSB4216 error pointing at an MSBuild.exe that never existed. Centralize the apphost-vs-fallback decision in a new NodeProviderOutOfProcTaskHost.ResolveNetTaskHostLaunchPath helper and use it from both the launcher (ResolveAppHostOrFallback) and the error path (TaskHostTask.LogErrorUnableToCreateTaskHost) so the reported path always matches the path that was actually launched. This is a logging-only change; launch behavior is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates MSBuild’s out-of-proc .NET task host handling so MSB4216 reports the actual launch target path for Runtime="NET" task hosts (app host vs dotnet-hosted MSBuild.dll), aligning user-facing diagnostics with the launcher’s resolution logic.
Changes:
- Centralizes apphost-vs-fallback selection in
NodeProviderOutOfProcTaskHost.ResolveNetTaskHostLaunchPath. - Uses the shared resolver in both the launcher (
ResolveAppHostOrFallback) and MSB4216 error construction (TaskHostTask.LogErrorUnableToCreateTaskHost). - Updates trace output and fallback command line to consistently use the resolved launch path.
Show a summary per file
| File | Description |
|---|---|
| src/Build/Instance/TaskFactories/TaskHostTask.cs | Uses the shared resolver to report the resolved NET task host launch path in MSB4216 instead of always pointing at the app host. |
| src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs | Introduces ResolveNetTaskHostLaunchPath and updates launcher logic to use the resolved app host or MSBuild.dll fallback consistently. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 3
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Note
🔒 Integrity filter blocked 2 items
The following items were blocked because they don't meet the GitHub integrity level.
- #13889
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - #13889
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
To allow these resources, lower min-integrity in your GitHub frontmatter:
tools:
github:
min-integrity: approved # merged | approved | unapproved | noneGenerated by Expert Code Review (on open) for issue #13889 · ● 20.3M
Cover the app-host-present and app-host-missing branches of the helper, ensuring the MSB4216 error path stays aligned with the launcher logic. Addresses review feedback on dotnet#13889. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
Author
|
@rainersigwald @JanProvaznik would appreciate your review on this one. Straightforward minimal change to log the actual invocation instead of always the apphost. |
rainersigwald
approved these changes
May 28, 2026
This was referenced May 31, 2026
ViktorHofer
added a commit
that referenced
this pull request
Jun 19, 2026
## Summary `Runtime="NET"` task host launches fail with `MSB4216` whenever the worker node and TaskHost node architectures don't line up the way the original implementation expected. Tracked as [#13879](#13879) (Bug A). > Terminology: the **worker node** is the MSBuild process that launches the task host (the "parent"); the **TaskHost node** is the launched `Runtime="NET"` task host process (the "child"). This wording was adopted following review feedback. Two coordinated handshake changes — one on each side — close every reasonable worker/TaskHost arch combination: ### 1. Worker node side — `CommunicationsUtilities.GetHandshakeOptions` For a NET task host the launched TaskHost node's architecture is whatever the .NET SDK shipped, not the worker node's. Emitting the worker node's arch bit produces a wire-level mismatch with already-shipped SDK TaskHost nodes whose arch differs. Suppress the `X64`/`Arm64` bit when invoked by the worker node for a NET task host (detected by `Runtime="net"` in the explicit `TaskHostParameters`). The TaskHost-node path (`TaskHostParameters.Empty`) is unaffected, so already-deployed worker nodes that still emit an arch bit continue to match. ### 2. TaskHost node side — `NodeEndpointOutOfProcBase.IsAllowedBitnessMismatch` The .NET task host side relaxation that lets a worker node without an arch bit on the wire connect to an SDK TaskHost node. Previously only tolerated `expected = X64`, so any arm64 SDK TaskHost node rejected the connection. Now also tolerates `expected = Arm64`. True cross-arch mismatches (worker `X64` ↔ TaskHost `Arm64`) remain rejected. Method is promoted to `internal static` so the test project can exercise the tolerance matrix directly. It was a stateless pure predicate already. ## Effect across worker node / SDK combinations `current` = behavior with this PR applied (worker-node change in VS MSBuild, TaskHost-node change in a future SDK). For each combo, "current behavior" notes whether the SDK side needs to update. | Worker node | TaskHost node (SDK MSBuild.dll) | Wire arch bit | Old behavior | Current behavior | |---|---|---|---|---| | x86 .NET Fx VS | x64 SDK | none → x64 | ✅ (existing `expectedIsX64` tolerance) | ✅ | | x86 .NET Fx VS | arm64 SDK | none → arm64 | ❌ MSB4216 (Bug A) — **the user's binlog** | ✅ once SDK ships with TaskHost-side `expectedIsArm64` tolerance | | amd64 .NET Fx VS | x64 SDK | x64 → x64 | ✅ (matches exactly) | ✅ (after worker fix: no bit on wire; already-shipped SDK tolerates none → x64) | | amd64 .NET Fx VS | arm64 SDK | x64 → arm64 | ❌ MSB4216 | ✅ once VS picks up worker-side suppression; SDK must also have TaskHost-side Arm64 tolerance | | arm64 .NET Fx VS | x64 SDK | arm64 → x64 | ❌ MSB4216 | ✅ once VS picks up worker-side suppression (works against already-shipped SDKs) | | arm64 .NET Fx VS | arm64 SDK | arm64 → arm64 | ✅ (matches exactly) | ✅ | | old worker node (pre-this-PR) | new SDK TaskHost node | x64/arm64 → matching | unchanged | unchanged — still works if arches match, still hits Bug A if they don't (worker has no fix) | ## Where each fix takes effect | Failing combo | Fixed by | Ships in | |---|---|---| | x86 .NET Fx VS → win-arm64 SDK | TaskHost-side Arm64 tolerance | a new .NET SDK build that picks up this PR | | amd64 .NET Fx VS → win-arm64 SDK | worker-side arch suppression + TaskHost-side Arm64 tolerance | next VS insertion of MSBuild (worker side) plus a new SDK build (TaskHost side) | | arm64 .NET Fx VS → win-x64 SDK | worker-side arch suppression (existing `expectedIsX64` tolerance already covers the TaskHost node) | next VS insertion of MSBuild only — no SDK update needed | The reported binlog (Roslyn build on Windows-ARM, SDK 10.0.108, x86 VS 18 MSBuild worker node) is the first row — needs the SDK side to pick this up. ## Tests `src/Build.UnitTests/BackEnd/NodeEndpointOutOfProcBase_Tests.cs` (6 cases): - `NoArchBitWorkerNode_X64TaskHost_IsTolerated` - `NoArchBitWorkerNode_Arm64TaskHost_IsTolerated` - `X64WorkerNode_X64TaskHost_NotConsideredMismatch` - `X64WorkerNode_Arm64TaskHost_NotTolerated` - `Arm64WorkerNode_X64TaskHost_NotTolerated` - `NoArchBitWorkerNode_NoArchBitTaskHost_NotTolerated` (guards against a future simplification to `return receivedIsX86;`) `src/Build.UnitTests/BackEnd/CommunicationsUtilities_Tests.cs` (5 cases): - `GetHandshakeOptions_NetTaskHostWorkerNode_SuppressesArchBit` × { x64, arm64, x86 } - `GetHandshakeOptions_NonNetTaskHostWorkerNode_KeepsX64ArchBit` - `GetHandshakeOptions_NonNetTaskHostWorkerNode_KeepsArm64ArchBit` - `GetHandshakeOptions_NetTaskHostNode_KeepsArchBit` All 11 pass on net10.0. ## References - Closes #13879 (Bug A). - Companion logging fix: #13889. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13888
Summary
When a
Runtime="NET"task host launch fails, the MSB4216 error messagealways pointed at
<sdk>\MSBuild.exe— the .NET task host app host — evenwhen the launcher fell back to
dotnet.exe MSBuild.dllbecause no app hostwas present in the SDK.
SDK 10.0.x ships
MSBuild.dllonly; the app host first appears in 10.0.300.On those SDKs users see an MSB4216 referencing a file that never existed
(see #13879 for a concrete repro on Windows-ARM with SDK 10.0.108).
Change
Centralize the apphost-vs-fallback decision in a new
NodeProviderOutOfProcTaskHost.ResolveNetTaskHostLaunchPathhelper thatreturns
(LaunchPath, UseAppHost). Use it from both the launcher(
ResolveAppHostOrFallback) and the error path(
TaskHostTask.LogErrorUnableToCreateTaskHost) so the reported pathalways matches the path that was actually launched.
This is a logging-only change; launch behavior is unchanged.
Notes
This is a partial fix for #13879. The underlying handshake bug
(
IsAllowedBitnessMismatchmissing Arm64 tolerance) remains and will befixed separately.