Repository navigation
Bug: ordinary dotnet clean leaves Copilot wrapper test prerequisite caches under obj\mxcw #1620
Copy link
Copy link
Closed
Labels
bugSomething isn't workingSomething isn't workingdiagnosedRoot cause identifiedRoot cause identifiedverified-locallyImplementation has been verified locallyImplementation has been verified locally
Description
Summary
dotnet clean (project or solution) does not clean the Copilot wrapper test prerequisite cache created before Phantom.Workspaces.Install.Tests runs. Repeated test runs can leave completed directories such as Phantom.Workspaces.Install.Tests\obj\mxcw\<hash>\prepared\phantom-copilot-wrapper.exe, even after ordinary MSBuild Clean. The build/test prerequisite should be owned by the normal Clean lifecycle, not require a separate manual cleanup command. This is diagnosed from code; a surviving cache was not reproduced in the current worktree (the sample cache was no longer present), so this is not a claim of an empirical dotnet clean run.
Root Cause
features/Phantom.Workspaces.Install.Tests/Phantom.Workspaces.Install.Tests.csproj:32-47attaches creation to VSTest only, with neither a Clean target nor MSBuildFileWrites/incremental-clean registration for the cache or pointer:The ordinary clean of build outputs does not know it must remove all hashed cache entries; the<Target Name="PrepareCopilotWrapperTestPrerequisite" BeforeTargets="VSTest" ...> <_CopilotWrapperPrerequisiteCache>$(MSBuildProjectDirectory)\$(BaseIntermediateOutputPath)mxcw</_CopilotWrapperPrerequisiteCache> <_CopilotWrapperPrerequisitePathFile>$(MSBuildProjectDirectory)\$(IntermediateOutputPath)copilot-wrapper-prerequisite.path</_CopilotWrapperPrerequisitePathFile> <Exec Command="pwsh ... -CacheRoot "$(_CopilotWrapperPrerequisiteCache)" -PathOutputFile "$(_CopilotWrapperPrerequisitePathFile)"" ... /> <Copy SourceFiles="$(_CopilotWrapperPrerequisitePathFile)" DestinationFiles="$(OutDir)copilot-wrapper-prerequisite.path" ... /> </Target>
BeforeTargets="VSTest"preparation is not a Clean handler.features/scripts/prepare-copilot-wrapper-test-prerequisite.ps1:292-312,337-350hashes every tracked and non-ignored untracked file (plus toolchain inputs) into the source fingerprint. Even an unrelated changed file can produce another cache key. At:374-391only the last pointer's verified cache is reused; at:832-857a new immutable entry is moved into the cache root and the pointer is replaced.:860-874removes transient staging/production intermediate paths but not previous completed entries. This explains why stale copies accumulate; narrowing the fingerprint is separate from the requested Clean integration.features/Phantom.Workspaces.Install.Tests/MxcIntegrationTests.cs:1436-1493reads the copied pointer from the test output and expects the immutableobj\mxcw\<key>\preparedartifact.features/Phantom.Workspaces/Phantom.Workspaces.csproj:125-133also validates that a production publish's prepared payload lies in the canonical Install.Tests cache root. Clean must not run preparation or alter the provenance checks.
Affected Files
| File | Role |
|---|---|
features/Phantom.Workspaces.Install.Tests/Phantom.Workspaces.Install.Tests.csproj |
Defines cache root and pointer/output copy; needs an ordinary MSBuild Clean hook. |
features/scripts/prepare-copilot-wrapper-test-prerequisite.ps1 |
Creates immutable hashed entries; cleanup should share its root/path validation and concurrency contract (or use a narrowly scoped companion script). |
features/Phantom.Workspaces.Install.Tests/MxcIntegrationTests.cs |
Existing prerequisite path/manifest assertions; add build/Clean lifecycle coverage using its test naming conventions. |
features/Phantom.Workspaces/Phantom.Workspaces.csproj |
Existing publish-time canonical cache provenance guard; retain it and reconcile any explicitly supported custom intermediate path behavior. |
Design / Fix
- Hook cleanup into ordinary
Cleanof Install.Tests (therefore solution Clean), without invokingPrepareCopilotWrapperTestPrerequisiteor requiring a new command. E.g. a project targetBeforeTargets="Clean"(or a verified equivalent normal Clean hook) calls a dedicatedpwsh -File ... -CacheRoot "..." -PathOutputFile "..." -CopiedPathFile "$(OutDir)copilot-wrapper-prerequisite.path"operation. Evaluate and quote the same MSBuild paths as preparation; do not depend on the pointer to discover all historical<hash>entries. Cover command ordering so SDK Clean does not leave or regenerate the pointer/copy. - Validate the canonical cache root against the project's evaluated
BaseIntermediateOutputPath(including a supported custom path containing spaces), and constrain deletion to the project-ownedmxcwtree plus that build's pointer and copied output. Do not follow reparse points or accept a pointer to an arbitrary external path; reject unsafe roots rather than deleting elsewhere. Clear all completed hashed entries and stale staging under the validated root, not just the latest key. Repeat Clean should succeed when everything is absent. - Coordinate Clean and prerequisite creation/consumption at the cache-root level (a per-fingerprint creation mutex alone is insufficient for an all-keys deletion). Prevent racing active builds/tests from losing their immutable inputs: hold an appropriate lease/lock across use or fail Clean explicitly before partial deletion if the root is in use; do not silently swallow locked-file failures. Restore/build/test after Clean must recreate and validate the prerequisite normally; production publish provenance remains fail-closed. Keep the broad fingerprint behavior out of scope for this fix except to test that multiple historical keys are removed.
Expected Tests
| Test Name | Class | What It Verifies |
|---|---|---|
CopilotWrapperPrerequisite_CleanAfterMultipleFingerprints_RemovesAllCacheEntriesAndPointers |
CopilotWrapperNestedPublishTests |
Ordinary project dotnet clean removes all completed/staging entries, intermediate pointer and copied output pointer without preparing again. |
CopilotWrapperPrerequisite_SolutionClean_RemovesInstallTestCache |
BuildIntegrationTests |
Ordinary solution Clean also triggers the project cleanup. |
CopilotWrapperPrerequisite_RepeatedClean_IsIdempotent |
CopilotWrapperNestedPublishTests |
A second Clean with already-absent entries succeeds. |
CopilotWrapperPrerequisite_CustomBaseIntermediateWithSpaces_CleansOnlyCanonicalRoot |
CopilotWrapperNestedPublishTests |
Evaluated custom base path (including spaces) is handled without deleting anything outside the owned root. |
CopilotWrapperPrerequisite_CleanWhileInUse_FailsWithoutPartialDeletion |
CopilotWrapperNestedPublishTests |
A concurrent active prerequisite build/test is protected; Clean fails clearly or waits under an explicit safe locking policy. |
CopilotWrapperPrerequisite_CleanThenTest_RecreatesValidatedCache |
CopilotWrapperNestedPublishTests |
A subsequent build/test restores the pointer and verified immutable manifest/artifacts. |
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingdiagnosedRoot cause identifiedRoot cause identifiedverified-locallyImplementation has been verified locallyImplementation has been verified locally
Activity
JoshuaRowePhantom commentedon Oct 8, 2026
Verification result
Status: ❌ 4 issue(s) found
Commits checked:
26ac0961 Clean Copilot wrapper prerequisite cache with project Clean(againstfeatures)Outstanding gaps
CopilotWrapperPrerequisite_CleanThenTest_RecreatesValidatedCacheexceededMxcRepositoryTestSupport.InvokeAsync's 10-minute process timeout during real prerequisite preparation (ProcessRunner.cs:466, test line 123); no rebuilt cache/manifest was verified. The first default run also aborted its test host at the approved runner's 90-second inactivity threshold during this test. This expected test must pass under the approved runner.MxcRepositoryTestSupport.LoadCopilotWrapperPrerequisiteinMxcIntegrationTests.csand_PreparedCopilotPayloadExpectedCacheRootinPhantom.Workspaces.csprojstill requireInstall.Tests\obj\mxcw, whereasPrepareCopilotWrapperTestPrerequisitenow emits to the evaluated customBaseIntermediateOutputPath\mxcw. The new custom-path test checks only Clean, not a normal test/publish consuming the custom cache. Reconcile this without weakening fail-closed provenance, or explicitly reject custom preparation rather than emit an unusable pointer.prepare-copilot-wrapper-test-prerequisite.ps1ends before VSTest starts; the test host acquires a shared lease only inLoadCopilotWrapperPrerequisite, then releases it after each individual consumer. Ordinary Clean can acquire the root lock and remove the copied pointer/cache while a test run is active between preparation and loading, or between test cases.CleanWhileInUsemanually locks the file and does not test this actual lifecycle race.OutDir), or an unexpected cache-root entry. These are new fail-before-any-deletion branches inCopilotWrapperPrerequisiteCache.ps1and the cleaner, and require verification without affecting outside files or previously existing cache entries.Tests
Approved runner from
worktrees\16:scripts\run-tests.ps1 -Mode fast -TestNames CopilotWrapperPrerequisite_: 3 passed, 0 failed, then test host aborted for 90-second inactivity whileCleanThenTestprepared its prerequisite (exit 1; hang dumps collected). Rerun usingscripts\run-tests.ps1 -Mode fast -NoBuild -PerTestHangTimeout 20m -TestNames CopilotWrapperPrerequisite_CleanAfterMultipleFingerprints,CopilotWrapperPrerequisite_RepeatedClean,CopilotWrapperPrerequisite_CleanWhileInUse,CopilotWrapperPrerequisite_ExternalIntermediateRoot,CopilotWrapperPrerequisite_LockedCacheFile,CopilotWrapperPrerequisite_CleanThenTest: Executed 6; Passed 5; Failed 1.scripts\test-results.log:System.TimeoutException: Process did not complete within the allotted 00:10:00inCopilotWrapperPrerequisite_CleanThenTest_RecreatesValidatedCache. Previous run additionally passedSolutionClean,ReparsePoint, andCustomBaseIntermediateWithSpaces(3/3). Filesystem cleanup requires slow tests at checkin;needs-slow-testsapplied.verified-locallynot applied.JoshuaRowePhantom commentedon Oct 9, 2026
Verification result
Status: ✅ Verified
Commits checked:
26ac0961andb6a49cc0(againstfeatures; tipb6a49cc0b71fa315c9d4bb0479a6cf384c0952a5). Source and submodules synchronized withcheckout-source.ps1before inspection.Outstanding gaps
None — all criteria satisfied.
The ordinary project/solution Clean hook validates ownership and evaluated intermediate paths, preflights pointers, reparse points, unexpected cache entries and locked files before deletion, and removes historical entries without invoking preparation. The shared/exclusive cache-root lease spans preparation, MSBuild/VSTest startup, test-host consumption and prepared publish; Clean fails while in use. Custom paths with spaces are accepted within the Install.Tests project, and the prepared publish retains fail-closed cache-root and artifact validation. Cold Clean → prerequisite recreation → custom-cache publish and final Clean completed successfully.
Tests
From
worktrees\16:scripts\run-tests.ps1 -Mode fast -NoBuild -PerTestHangTimeout 20m -TestNames CopilotWrapperPrerequisite_— Executed: 18; Passed: 18; Failed: 0, including the previously timed-out cold rebuild, active test-host Clean rejection, project/solution Clean, custom base and unsafe-path cases. Confirmed each result in the Install.Tests TRX andscripts\test-results.log. Filesystem changes require slow tests at checkin;needs-slow-testsis already present.Merge pull request #1626 from JoshuaRowePhantom/integration/release-1…
JoshuaRowePhantom commentedon Oct 9, 2026
Checked into features via #1626 (merge 1b86a98). Hosted fast CI 37966216282 passed 8,019/8,019; full CI 37966209695 passed on Windows and Linux. #1617 and #1618 retain their separate acceptance gates.