Repository navigation
Bug: Install.Tests testhost hang after MxcRuntimePayload_RequiredNativeUnit_IsPresent completes #1494
Description
Unrelated test failure — observed during fix for #1474
Failing test batch: Phantom.Workspaces.Install.Tests (test host aborted after all tests passed)
Test running when hang was detected: Phantom.Workspaces.Install.Tests.MxcRuntimePayloadTests.MxcRuntimePayload_RequiredNativeUnit_IsPresent
Symptom
run-tests.ps1 -Mode fast reports:
Passed! - Failed: 0, Passed: 138, Skipped: 0, Total: 138, Duration: 4 s - Phantom.Workspaces.Install.Tests.dll (net10.0)
...
=== Test Run Summary ===
Executed : 6441
Passed : 6441
Failed : 0
FAIL [jrowe_DAEMON_2026-09-07_18_39_41_net10.0]: test host crashed (outcome=Failed but no test failures)
The vstest console log for the Install.Tests project shows:
Test host process crashed : You are up to date; no newer release is available.
Update staged: version 0.2.0.
Update service is not available in this build.
Data collector 'Blame' message: The specified inactivity time of 90 seconds has elapsed. Collecting hang dumps from testhost and its child processes.
All 138 Install.Tests tests actually passed (Failed: 0, Passed: 138); the failure is a
test-host inactivity hang detected by Blame after the last test completed, coupled with
stray stdout from the app's auto-updater code getting interpreted by vstest as a "test host
crashed" reason line.
Dumps
Blame captured hang dumps from testhost and 17 child dotnet.exe processes. All dumps are
archived under dumps/<bug>/. The testhost main thread is parked in
DefaultEngineInvoker.Invoke waiting for xUnit's VsTestRunner.RunTests to complete; the
xUnit runner thread is blocked on RandomAccess.ReadAtOffset inside a nested
AsyncOverSyncWithIoCancellation I/O originating from a dotnet.exe child. The child
processes are the dotnet publish invocation launched by
MxcRuntimePayload_RequiredNativeUnit_IsPresent.
Parallel stacks (testhost, abridged)
System.Threading.Tasks.Task.Wait()
Microsoft.VisualStudio.TestPlatform.TestHost.DefaultEngineInvoker.Invoke(...)
Microsoft.VisualStudio.TestPlatform.TestHost.Program.Main(String[])
Xunit.Runner.VisualStudio.VsTestRunner.RunTests(...)
Xunit.Runner.VisualStudio.VsTestRunner+<RunTestsInAssembly>d__33.MoveNext()
System.IO.RandomAccess.ReadAtOffset(SafeFileHandle, Span<Byte>, Int64)
System.Threading.AsyncOverSyncWithIoCancellation+<InvokeAsync>d__8<...>.MoveNext()
Why unrelated to #1474
Branch #1474 modifies only:
Phantom.Workspaces.Llm.Core/Processes/ProcessExecutor.csPhantom.Workspaces.Llm.Core/Processes/WindowsCommandLine.csPhantom.Workspaces.Llm.Core/Shell/LocalShellStreamHandler.cs- The matching test files in
Phantom.Workspaces.Llm.Core.Tests/
None of these files are referenced by Phantom.Workspaces.Install.Tests,
MxcRepositoryTestSupport, Assert-MxcRuntimePayload.ps1, or the
Phantom.Workspaces.csproj publish targets exercised by the hanging test. The stability
run of the four changed test classes passed 10/10 immediately before the fast suite. The
crash reason text ("You are up to date … Update staged: version 0.2.0. Update service is
not available in this build.") comes from the auto-updater emitting messages on stdout
during dotnet publish / app startup, not from any Llm.Core process-executor code path.
Root cause hypothesis
- During
MxcRuntimePayload_RequiredNativeUnit_IsPresent,dotnet publish Phantom.Workspaces.csproj
is invoked. Something in the publish target chain (or in launched
Phantom.Workspaces.exestartup during a post-publish target) writes auto-updater
status text to stdout. That text is picked up by vstest as the "crash reason" because
the vstest protocol multiplexes over stdout. - Meanwhile the publish child process (or one of its
nodeReuse:falsemsbuild children)
holds a handle that the Xunit runner is reading viaRandomAccess.ReadAtOffsetand
never closes, so the runner is still blocked when Blame's 90-second inactivity timer
fires and dumps testhost + 17 dotnet children.
Proposed solution
- Silence the auto-updater output during
Phantom.Workspacespublish/startup when running
inside a test host (detectVSTEST_HOSTor equivalent env var) so that stray stdout
cannot masquerade as a test-host crash reason. - Ensure
MxcRepositoryTestSupport.InvokeAsyncfully drains and closes both stdout/stderr
pipes before returning (e.g.await Task.WhenAll(stdoutTask, stderrTask)and
process.WaitForExitAsyncon the same cancellation token) so no child handle survives
the test body. - Add
/nodeReuse:falseand/p:UseSharedCompilation=falseconsistently to any nested
dotnetinvocations, and dispose the returnedProcessexplicitly, so orphan msbuild
workers cannot keep the parent handle alive.
Once the auto-updater no longer writes to stdout in tests and the child pipes are drained,
the Blame inactivity timer will not fire on this test.
Activity
JoshuaRowePhantom commentedon Sep 8, 2026
Design
Root cause
MxcRuntimePayload_RequiredNativeUnit_IsPresentlaunches a nesteddotnet publishwhile capturing stdout and stderr with anonymous pipes. The helper correctly owns and disposes the directProcessand starts both drains before waiting, but EOF on those drains is controlled by every inherited writer. The publish starts reusable MSBuild/compiler-server descendants; those descendants can survive the directdotnetprocess and retain inherited pipe write handles, leavingReadToEndAsyncparked after the test assertions have otherwise completed. The archived failure's 17 childdotnetprocesses andRandomAccess.ReadAtOffsetstack match that ownership leak. The updater text is stderr payload from management-mode tests, not the lifecycle root cause, so suppressing it would only hide evidence.Chosen approach
Make this nested publish hermetic at its process-creation boundary: disable reusable build servers and shared compilation in addition to the existing MSBuild node-reuse switch. Keep draining both redirected streams and disposing the direct process; no timeout, sleep, output suppression, or test-host detection is needed. Add deterministic regression coverage first that verifies every required process-isolation switch is supplied by the publish test.
Alternatives considered
Files
Phantom.Workspaces.Install.Tests/MxcIntegrationTests.cs— regression assertion and isolated publish invocation.JoshuaRowePhantom commentedon Sep 8, 2026
Implementation notes
Commit: 388216c
Deviations from proposed design
None.
Scope changes
None. Updater output remains visible because it was diagnostic payload, not the pipe-lifetime cause.
Files changed
Phantom.Workspaces.Install.Tests/MxcIntegrationTests.csDiagnosis and verification
The nested publish allowed reusable MSBuild/compiler-server descendants to outlive the direct
dotnetprocess while retaining inherited anonymous-pipe writers. The test helper then remained inReadToEndAsyncwaiting for EOF. The publish now disables persistent build servers, MSBuild node reuse, and shared compilation at launch while preserving full stdout/stderr draining.Regression test was observed failing before the fix. Build completed with zero errors;
MxcRuntimePayloadTestspassed 8/8; targeted stability passed 10/10 runs (80/80 test executions), all with zero failures and no testhost hang.JoshuaRowePhantom commentedon Sep 8, 2026
Verification result
Status: ✅ Verified
Commits checked:
Outstanding gaps
None — all criteria satisfied.
Tests
JoshuaRowePhantom commentedon Sep 8, 2026
Verification result
Status: ✅ Verified
Commits checked:
Outstanding gaps
None — all criteria satisfied.
Tests
MxcRuntimePayloadTests: 8/8 passed in the prescribed targeted run.JoshuaRowePhantom commentedon Sep 8, 2026
Verification result
Status: ✅ Verified
Commits checked:
Outstanding gaps
None — all criteria satisfied.
Tests
MxcRuntimePayloadTests: 8/8 passed in the prescribed targeted run.Fix #1494: isolate nested publish build servers
JoshuaRowePhantom commentedon Sep 8, 2026
Checked in
Commit: 388216c
Branch fast-forwarded: features -> 388216c
JoshuaRowePhantom commentedon Sep 10, 2026
New occurrence — observed during check-in for #1489
Failing test:
Phantom.Workspaces.Install.Tests.MxcRuntimePayloadTests.MxcRuntimePayload_RequiredNativeUnit_IsPresentResult: All 144 tests passed, but vstest aborted after the 90-second inactivity timeout.
Dump archived:
dumps\1494\testhost_24604_20260910T123818_hangdump.dmp(plus 17 child-process dumps)Dump evidence
dumpasync --completed falseshowsMxcRepositoryTestSupport.InvokeAsyncstill awaiting bothProcess.WaitForExitAsyncand stdout/stderrReadToEndAsync.parallelstacksshows the xUnit runner blocked inRunTestsInAssembly, with two pipe reads inRandomAccess.ReadAtOffset. This is the same failure mechanism previously tracked here, despite commit388216c0.Root cause hypothesis
A descendant of the nested
dotnet publishstill inherits and retains the redirected output handles, so the direct process exit is insufficient to complete the drain tasks. The previous build-server isolation does not cover every descendant in this invocation.Proposed solution
Run the publish in a Windows Job Object configured to terminate the full process tree and prevent surviving descendants from retaining redirected handles; after process exit, explicitly close/cancel the pipe drains with a bounded timeout. Add a repeated regression test that verifies the helper completes after the published payload has been validated.
JoshuaRowePhantom commentedon Sep 11, 2026
Correction — #1490 failure is branch-related
The observed failure is not a new #1494 occurrence. #1494 tracks a post-test nested-publish hang; #1490 instead fails deterministically inside its new nested wrapper publish graph. No dump was produced.
PublishCopilotWrapperLoosepublishes the new wrapper after the parent application publish. The wrapper referencesLlm.Core -> Data.MongoDB -> Phantom.Workspaces.Containers, rebuildingPhantom.Workspaces.Containerswith the same Release/RID output path already used by the parent publish. Both fast-suite attempts failed while recreatingPhantom.Workspaces.Containers.runtimeconfig.jsonbecause it was locked. The #1490 publish target needs to isolate or avoid the duplicate project graph/output before check-in.69 remaining items
JoshuaRowePhantom commentedon Sep 15, 2026
Checked in
Commit: ab476c8
Branch fast-forwarded: features → ab476c8
Validation: build clean; changed-test stability 10/10 (28/28 each); fast suite 7,378/7,378. A cold clean-input run passed 28/28 with default Debug/Release
copilot.exeabsent, native build observed only before testhost, and zero dumps.JoshuaRowePhantom commentedon Sep 16, 2026
Release packaging recurrence
Workflow run 35084206887 passed the full test gate but failed production dotnet publish -c Release -r win-x64. PublishCopilotWrapperLoose passes relative obj\Release\net10.0\win-x64\copilot-wrapper\ as the child wrapper project's PublishDir. The child resolves that path under Phantom.Workspaces.Copilot.Cli.Wrapper and successfully publishes there, while the parent checks the same relative text under Phantom.Workspaces and reports the wrapper missing. Reopened for canonical absolute publish-path ownership and a clean production Release workflow regression.
JoshuaRowePhantom commentedon Sep 16, 2026
Design — retry 9 canonical wrapper publish ownership
Root cause
PublishCopilotWrapperLoosederives_CopilotWrapperPublishDirfrom the parent project's relativeIntermediateOutputPathand passes that relative text as the wrapper project'sPublishDir. MSBuild resolves the child property beneath the wrapper project, while the parent validates and copies beneathPhantom.Workspaces, so production release publish fails after the child succeeds.Chosen approach
Resolve a canonical, absolute, parent-owned wrapper staging directory before invoking the child. The directory will remain inside the canonical parent intermediate root, include configuration/RID and an invocation-unique component, use a normalized trailing separator, reject traversal and existing reparse-point ancestors, and be passed to the child with MSBuild escaping. Parent validation and copy consume the same exact output path. Prepared test-only/provenance paths remain build-free and unchanged.
Add regression coverage for the path contract, spaces/custom intermediate roots, independent concurrent invocations, and missing child output. Extend the pre-VSTest prerequisite to run an actual clean isolated Release/win-x64 production publish using the workflow command shape, validate the final payload and ZIP, and record evidence for testhost assertions so no cold native build moves into test execution.
Alternatives considered
Resolving the relative path against the child or merely prefixing
..preserves split ownership and traversal ambiguity. A deterministic shared directory collides for concurrent same-RID publishes. Quoting alone does not canonicalize ownership. Running the cold production regression inside testhost would recreate the 90-second Blame failure this issue already fixed.Files
Phantom.Workspaces/Phantom.Workspaces.csprojPhantom.Workspaces.Install.Tests/MxcIntegrationTests.csPhantom.Workspaces.Install.Tests/Phantom.Workspaces.Install.Tests.csprojscripts/prepare-copilot-wrapper-test-prerequisite.ps1JoshuaRowePhantom commentedon Sep 16, 2026
Implementation notes — retry 9 canonical wrapper publish ownership
Commit:
6721198a9877b70561845831c957583cf82a28d4Deviations from proposed design
The release-equivalent prerequisite uses a short explicit parent wrapper-intermediate root while keeping the rest of the production graph under isolated SDK artifacts. This avoids Windows MAX_PATH failures in the deliberately deep immutable prerequisite cache; ordinary production publishing still derives directly from the parent
IntermediateOutputPath.The workflow validator exposed that Copilot CLI 1.0.83 no longer accepts
--version. Its startup smoke now launches with no server-mode arguments and requires the runtime's exact expected argument-validation response instead of skipping host-RID startup.Scope changes
Added explicit child-side equality validation for the parent-owned absolute
PublishDir, reparse-point rejection, invocation-unique configuration/RID staging, customBaseIntermediateOutputPathandIntermediateOutputPathcoverage (including spaces/traversal normalization), concurrent isolation, and a fail-closed missing-child test. Prepared test-only provenance remains build-free.Files changed
Phantom.Workspaces/Phantom.Workspaces.csprojPhantom.Workspaces.Copilot.Cli.Wrapper/Phantom.Workspaces.Copilot.Cli.Wrapper.csprojPhantom.Workspaces.Install.Tests/MxcIntegrationTests.csPhantom.Workspaces.Install.Tests/CopilotWrapperPackagingTests.csscripts/prepare-copilot-wrapper-test-prerequisite.ps1packaging/validate/Assert-CopilotRuntimePayload.ps1docs/design/build-and-installation.mdVerification
dotnet publish Phantom.Workspaces.csproj -c Release -r win-x64 -o <path with spaces>succeeded after deleting its Release intermediate, with identical absolute parent/child wrapper paths.JoshuaRowePhantom commentedon Sep 16, 2026
Verification result
Status: ❌ 1 issue(s) found
Commit checked (
features..HEAD):6721198a Fix #1494: canonicalize wrapper publish ownership(424 insertions, 7 files)Fresh independent re-verification (no reliance on implementer notes). The wrapper publish-ownership fix itself is sound, but the same commit introduces a new, release-workflow-breaking regression in the Copilot startup smoke — so the reopen's core requirement (a clean, green production
Release/win-x64publish) is still not met.Outstanding gaps (1)
1. Copilot startup-smoke change leaves
$LASTEXITCODE = 1, failing theshell: pwshpublish step (release.yml+publish-validation.yml).packaging/validate/Assert-CopilotRuntimePayload.ps1's smoke fromcopilot.exe --version(expects exit 0, as onmain) to a no-arg launch that expects exit 1:$LASTEXITCODE, so it stays 1.release.yml("Publish and package win-x64") andpublish-validation.yml, the validators run in-process inside a singleshell: pwshstep. The last native command in the loop body is this Copilot smoke; everything after it (Assert-MxcRuntimePayload.ps1,New-ReleaseZip.ps1,Assert-*Zip.ps1, the finalOut-File) is managed and never resets$LASTEXITCODE.pwshrunner writes the step to a temp.ps1and appendsif ((Test-Path -LiteralPath variable:\LASTEXITCODE)) { exit $LASTEXITCODE }. With$LASTEXITCODE = 1at step end, the step process exits 1 and the step FAILS even though every validation logically passed. (dotnet publishis the only native call with an explicitif ($LASTEXITCODE -ne 0){throw}guard; the smoke has none.)Reproduced (ironclad, current): ran the real
Assert-CopilotRuntimePayload.ps1+Assert-MxcRuntimePayload.ps1against a real clean-c Release -r win-x64payload (published without-p:Version), wrapped exactly as GitHub does ($ErrorActionPreference='stop'prefix + theexit $LASTEXITCODEsuffix, invoked viapwsh -command ". '<tempfile>'"):Corroboration:
publish-validation.ymlonmainis consistently green because its--versionsmoke ends with exit 0; no CI run exists for6721198a(local-only branch).copilot.exeno-arg exits 1 in ~50 ms with stderrcopilot-runtime: SDK server mode requires --server or --headless.Suggested fix (not applied — read-only): after a passing smoke, reset the code — e.g.
$global:LASTEXITCODE = 0(orexit 0) at the end ofAssert-CopilotRuntimePayload.ps1; ideally also wrap the native call intry/catchso it survives runners where$PSNativeCommandUseErrorActionPreferenceis$true(there the& $copilotExeexit-1 would throw before the check). Alternatively neutralise$LASTEXITCODEin the workflow steps after the payload assertions.Verified sound (no gaps)
ResolveCopilotWrapperPublishDirectorycomputes a canonical absolute dirPath.GetFullPath(<projectRoot>\<IntermediateOutputPath>)\cw\SafeSegment(Config)\SafeSegment(RID)\<16-hex GUID>and passes it to the child as bothPublishDirandCopilotWrapperParentPublishDirectory(MSBuild-escaped); childValidateParentOwnedPublishDirectory(AfterTargets=Publish) fails closed on any mismatch. Confirmed in the live test log: parent == child ==...\artifacts\cw-*\cw\Release\win-x64\<guid>, "child confirmed parent PublishDir".BaseIntermediateOutputPath/IntermediateOutputPath(incl. paths with spaces) resolve under the parent root; configuration/RID separated viaSafeSegment; per-invocation GUID isolates concurrent publishes; reparse/junction rejected; path traversal confined; trailing-separator/escaping handled; no local/absolute paths baked into published output or validators.dotnet publish -c Release -r win-x64, no-p:Version) yields a correct nested payload; all four validators (Copilot payload/zip, MXC payload/zip) pass logically. The MXC-p:Versionconcern was a local-harness artifact —Phantom.Workspacesdoes notProjectReferenceMicrosoft.Mxc.Sdk(MXC native is file-copied), andpublish-validation.ymlis green onmainwith-p:Version=0.0.0-validation..csprojproperties cannot redirect production output unsafely (parent-owned absolute path + fail-closed validation); the test-only prepared path/provenance still works without an in-testhost cold build (prep runs viaBeforeTargets=VSTest, manifest-cached).Tests
Targeted classes
CopilotWrapperNestedPublishTests, CopilotWrapperPackagingTests, MxcRuntimePayloadTests, MxcSdkVersionTestsviascripts\run-tests.ps1— 38/38 passed, 0 failed, 0 crash dumps.10-run stability (
run-tests.ps1, back-to-back,-NoBuild): 10/10 × 38/38, 0 failures, 0.dmp.Verdict: issues:1. The publish-ownership fix is verified sound, but the accompanying startup-smoke change must reset
$LASTEXITCODE(or the workflow must neutralise it) before this can produce a green production Release publish.JoshuaRowePhantom commentedon Sep 16, 2026
Design — retry 9 verification correction
The single outstanding gap is confined to the Copilot payload validator. Capture the no-argument runtime's expected exit code while temporarily disabling
PSNativeCommandUseErrorActionPreference, preserving merged stdout/stderr and converting actual launch failures to explicit validator failures. Validate exit/message, then reset$global:LASTEXITCODEto zero so GitHub's injected pwsh epilogue cannot reinterpret accepted exit 1 as step failure.Add a child-pwsh regression that reproduces GitHub's
ErrorActionPreference=Stop, native-error preference, final-command invocation, andexit $LASTEXITCODEepilogue against the real prepared runtime. The same seam will prove an unexpected real executable exit and an unlaunchable executable still fail. Audit confirms no other release validator directly accepts a native nonzero code; ZIP validation delegates to this validator and inherits the reset.JoshuaRowePhantom commentedon Sep 16, 2026
Retry 9 verification correction implemented
Commit:
f04c8524bccf8aab60762806e3a1ccb7474e77c9finally.LASTEXITCODEto 0, including ZIP-validator delegation.pwshcoverage reproducing GitHub's stop preference and finalLASTEXITCODEepilogue under both normal andPSNativeCommandUseErrorActionPreference=true; accepted exit 1 returns 0, while real unexpected-exit and launch-failure cases return nonzero.packaging/validatescripts: no other validator directly accepts a nonzero native exit.Assert-CopilotRuntimeZip.ps1delegates to the corrected payload validator.Validation:
LASTEXITCODE=0.No push, amend, or rebase performed.
JoshuaRowePhantom commentedon Sep 16, 2026
Verification result
Status: ✅ Verified
Branch:
fix/1494-canonical-wrapper-publish-retry9· worktreeworktrees\9Commits checked (
featuresc3336da0→ HEAD):Independent, reasoning-isolated re-verification of the entire retry-9 change (base
6721198aand correctionf04c8524, reviewed as a whole, not just the final diff). The prior authoritative verification (the 15:27 comment) recorded a single gap — the no-arg Copilot startup smoke leaving$LASTEXITCODE = 1and failing theshell: pwshpublish step. That gap is now closed and every contract is independently proven.Outstanding gaps
None — all criteria satisfied.
Contract evidence
1. Canonical, parent-owned wrapper publish path (exactly passed, validated, copied, checked).
ResolveCopilotWrapperPublishDirectory(Roslyn task, parent.csproj) computesPath.GetFullPath(<projectDir>\<IntermediateOutputPath>)\cw\SafeSegment(Config)\SafeSegment(RID)\<16-hex GUID>\, rejects reparse-point ancestors,SafeSegmentneutralises traversal/..(.→_), enforces aStartsWith(intermediatePrefix)containment guard, and emits a normalized trailing separator. It is passed to the child as bothPublishDirandCopilotWrapperParentPublishDirectory([MSBuild]::Escape). The child'sValidateParentOwnedPublishDirectory(AfterTargets=Publish) fails closed on any canonical mismatch; the parent then!Exists-checks and copies from that same path intoruntimes\<rid>\native.dotnet publish -c Release -r win-x64(deleted Release intermediates first) → exit 0, log proved parentCopilot wrapper child PublishDir:== childconfirmed parent PublishDir:==...\obj\Release\net10.0\win-x64\cw\Release\win-x64\e06e637bea744c3e(identical absolute path; config/RID separated; invocation-unique).IntermediateOutputPath/BaseIntermediateOutputPathwith spaces +..stay under the canonical root; two concurrent same-RID publishes resolve to distinct invocation dirs; a junction intermediate is rejected withcannot traverse reparse pointbefore any child output;SimulateMissingCopilotWrapperChildOutputForTestsproves the!Existscheck fails closed (Copilot MXC wrapper was not published).2. Prepared inputs stay test-only / build-free / provenance-verified; cold native prerequisite stays outside testhost.
ValidatePreparedCopilotPayloadrequiresUsePreparedCopilotPayloadForTests=true(test-only),NoBuild=true(build-free), a fully-qualified existingPreparedCopilotPayloadDirectory(canonical-path restricted), rejects legacy properties, gates the newSimulateMissing…flag totrue-only, and hashes every prepared artifact against its provenance SHA-256. The cold production publish runs fromscripts\prepare-copilot-wrapper-test-prerequisite.ps1via<Target BeforeTargets="VSTest"><Exec>pwsh …</Exec>— an MSBuild-phase step outside the testhost process (the original hang cause), manifest-cached (schema v4,ProductionPublishValidated).3. No-arg Copilot smoke — real process-exit behaviour.
The smoke launches
copilot.exewith native-error promotion temporarily disabled (restored infinally), requires exit 1 andSDK server mode requires --server or --headless, converts launch failures to explicit throws, and resets$global:LASTEXITCODE = 0on success. Verified by actual child-process exit code, not script state:RuntimePayload_GitHubFinalCommand_AcceptedNativeExitIsClean(Theory,PSNativeCommandUseErrorActionPreference∈ {false,true}) → childpwsh -Filewith GitHub'sexit $LASTEXITCODEepilogue exits 0.whoami.exe, exit 0) → fails withdid not reach expected server-mode argument validation (exit 0).copilot.exe could not be launched.release.ymlpost-publish body (Copilot payload → MXC payload →New-ReleaseZip→ Copilot zip → MXC zip) insidepwsh -Filewith$ErrorActionPreference='stop',$PSNativeCommandUseErrorActionPreference=$true, and theexit $LASTEXITCODEepilogue, against the real clean Release/win-x64 payload →STEP-BODY-COMPLETED LASTEXITCODE=0and child process exit code = 0 (smoke validated server mode twice).4. Sibling validator audit.
Only
Assert-CopilotRuntimePayload.ps1accepts a non-zero native exit.Assert-MxcRuntimePayload,Assert-MxcSdkVersion(in-processNativeLibrary.Load, no subprocess),Assert-MxcRuntimeZip,Assert-CopilotSdkVersion, andNew-ReleaseZipare managed/in-process and never leave a non-zero$LASTEXITCODE;Assert-CopilotRuntimeZipdelegates to the corrected payload validator and inherits its reset. The end-to-end step ending at exit 0 confirms no sibling leak.5. Tests / stability / real publish. See below. (Data-flow: parent path → child validation → copy → payload → zip → validator, every link exercised.)
Note (non-blocking, no gap): the smoke's
exit==1 & wrong-outputsub-branch has no dedicated synthetic test; the throw path (via the wrong-exit case) and the output-regex positive (via the real-copilot.exeaccepted case) are both covered, so a regex regression would surface. Recorded only for transparency.Tests
Targeted via
scripts\run-tests.ps1 -Mode fast -TestNames CopilotWrapperNestedPublishTests,CopilotWrapperPackagingTests,MxcRuntimePayloadTests,MxcSdkVersionTests(never directdotnet test):10-run stability (
-NoBuild, isolated) — 10/10 × 42/42, 0 failed, 0.dmp:Real clean
-c Release -r win-x64publish + fullrelease.ymlvalidator sequence (GitHub-faithfulpwshstep) → exit 0. No skipped/quarantined tests, noTask.Delay/Thread.Sleep/polling (a test even asserts the prep script has noStart-Sleep), no new UI-thread/locksync-blocking, no unbacked TODOs. Read-only: no source edits, commits, pushes, or merges; scratch kept under gitignored paths and removed; tracked worktree clean; 0 crash dumps.Verdict: verified — the reopen's core requirement (a clean, green production Release/win-x64 publish + validator path with correct canonical wrapper ownership and no leaked shell status) is met.
Fix #1494: canonicalize wrapper publish ownership
JoshuaRowePhantom commentedon Sep 16, 2026
Checked in
Commit: f04c852
Branch fast-forwarded: features → f04c852