Repository navigation
Reject terminal-error Windows Sandbox viewers - #889
Conversation
Recognize the bounded nonlocalized Sandbox error-page structure, preserve unknown-viewer ambiguity and ownership fencing, and verify rendering before reporting readiness or parking a client. Closes #887 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A viewer whose identity changes during inspection remains a candidate and can still cause false ambiguity.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
What changed in this PR
Adds fail-closed Windows Sandbox viewer classification so terminal-error windows are not treated as usable desktops.
Changes:
- Classifies session, terminal-error, and unknown surfaces via bounded UI Automation.
- Applies classification to ownership, readiness, and placement.
- Adds regression tests and user guidance.
| File | Description |
|---|---|
NativeMethods.txt |
Adds UI Automation interop types. |
WindowsSandboxWindowController.cs |
Filters and validates viewer candidates. |
SandboxClientErrorProbe.cs |
Implements surface classification. |
WindowsSandboxWindowControllerTests.cs |
Tests selection and readiness behavior. |
SandboxClientErrorProbeTests.cs |
Tests classifier safety boundaries. |
SKILL.md |
Adds troubleshooting guidance. |
sandbox-execution.md |
Documents snapshot readiness behavior. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Discard vanished windows and preserve replacement windows as fresh unknown candidates without inheriting launcher evidence. Cover vanished, zero-handle, HWND replacement, and start-time replacement cases. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Build Metrics ReportValidation passed. All required build and validation jobs succeeded. Binary Sizes
.NET Test Results (TRX reports)Other suites are reflected in the overall validation status above. ✅ 7756 passed, 37 skipped out of 7793 tests in 1137.1s (+45 tests, -224.6s vs. baseline) Test Coverage✅ 85.6% line coverage, 80.1% branch coverage · CLI Startup Time62ms median (x64, Try This BuildInstalls the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing. & ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 889Switching between builds often?Put the tool on your PATH once: & ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPathThen this build is just: winapp-pr 889Run Updated 2026-09-22 03:19:10 UTC · commit |
Suppress remembered preference when any retained viewer is unknown. Preserve exact remembered preference among verified sessions and reject input readiness before moving windows. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Post-restore validation can still report readiness when an unknown viewer appears during window restoration.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
Resolved since last review (1)
Reuse the same candidate-set policy before and after parking. Reject a newly appeared unknown viewer for input or capture while preserving verified remembered-session preference and terminal-error filtering. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The private Windows Sandbox UI structure requires final human review and exact-head live validation despite the successful build and 62 focused tests.
Review effort: Balanced
Findings: None
Resolved since last review (1)
|
State: agent-preparing — current CI and independent delta coverage; addressed-feedback re-review alone is not a readiness blocker under the user's updated policy. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The polling classifier does not deterministically release UI Automation COM wrappers, allowing native references to accumulate during connection waits.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Builds and 62 focused tests pass, but no running Sandbox was available to validate the private UI Automation structure against a real viewer.
Review effort: Balanced
Findings: None
Resolved since last review (1)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Zach Teutsch (zateutsch)
left a comment
There was a problem hiding this comment.
🤖 AI-generated review (winappcli pr-review skill) — verify before acting.
Changes requested — the terminal-error rejection logic, ownership filtering, docs, and tests all hold up, and a NativeAOT publish + the 62 sandbox tests pass. One high-severity gap remains: SandboxClientErrorProbe.Inspect is meant to fail closed to Unknown on any inspection failure, but two real paths escape its catch and can crash the read-only winapp target snapshot sandbox command outside its --json contract. Details inline. The remembered-window adoption/ambiguity behaviors look intentional (each is pinned by a test) and are safe given only one Sandbox VM runs at a time, so nothing else is blocking.
Treat access-denied and invalid-window HRESULT exceptions as unknown viewer evidence, guard a missing native root, and cover the actual exception mapping and structured snapshot route without swallowing programming errors. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Zach Teutsch (zateutsch)
left a comment
There was a problem hiding this comment.
🤖 AI-generated re-review (winappcli pr-review skill) — verify before acting.
Approving — the blocking finding from my earlier review is fully resolved by 4c811839 ("Handle mapped UIA failures without breaking snapshot JSON"), and I re-validated the current head d6eb8175.
What was fixed in SandboxClientErrorProbe:
- The catch now also swallows
UnauthorizedAccessExceptionandArgumentException, the real types thatE_ACCESSDENIED(0x80070005) andE_INVALIDARG(0x80070057) map to — so an inaccessible or racing UIA provider now fails closed toUnknowninstead of escaping and crashingwinapp target snapshot sandbox --jsonoutside its JSON contract. - A missing native root is guarded by the new
IsExpectedRoot(root, processId)helper ([NotNullWhen(true)]), closing theNullReferenceExceptionpath whenElementFromHandlereturns null on a closing window. - The tests were corrected and strengthened: the bogus hand-built
COMException(0x80070005)was replaced, andInspect_MappedProviderFailure_RetainsUnknownnow drives the real runtime mapping viaMarshal.ThrowExceptionForHR, pinning the regression that previously slipped through. Good restraint too —Inspect_ProgrammingFailure_IsNotSilentlyConvertedToUnknownverifiesInvalidOperationExceptionstill propagates, so the catch wasn't over-broadened to swallow programming errors.
Validation on d6eb8175: dotnet build WinApp.Cli.Tests — succeeded, 0 warnings / 0 errors; ran SandboxClientErrorProbeTests + WindowsSandboxWindowControllerTests — 66/66 passed (was 62; +4 targeted tests).
The merge from main also pulled in unrelated changes (cert-generate DN validation, find-ui/find-api, control-snippet) — those are separately merged PRs, out of scope for this review. Nothing blocking remains on the Sandbox change.
Trivial nit, non-blocking: the fix left var walker = automation.get_RawViewWalker(); in ReadSurface with stray extra indentation. No analyzer flags it; clean up if you touch the file again.
Use serializable expected exception names instead of Type-valued DataRows so discovery and execution account for both cases. Keep exact runtime mapping assertions and HRESULT execution unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…tigation' into nmetulev-sandbox-ownership-investigation
Resolve primitive row discriminators inside the test, retaining exact Type equality without deferring discovery. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Zach Teutsch (zateutsch)
left a comment
There was a problem hiding this comment.
🤖 AI-generated re-review (winappcli pr-review skill) — verify before acting.
Still approving — re-validated the current head c3c010e6. The only change to the Sandbox code since my last approval is a test-quality refinement in SandboxClientErrorProbeTests:
Inspect_MappedProviderFailure_RetainsUnknown's[DataRow]args moved fromtypeof(...)tonameof(...)string constants, so the rows are enumerable during MSTest discovery (aTypeargument isn't a discovery-time constant). The assertion was also tightened fromIsInstanceOfTypeto an exact-typeAssert.AreEqual(expectedType, mapped.GetType()). Both are strict improvements; the production fix from4c811839is unchanged.
Validation on c3c010e6: dotnet build WinApp.Cli.Tests — succeeded, 0 warnings / 0 errors; SandboxClientErrorProbeTests + WindowsSandboxWindowControllerTests — 66/66 passed.
Everything else in the range (get-focused ownership #880, lifecycle-docs #915, ui-automation doc/service edits) came in via merges from main and is out of scope for this PR's Sandbox change. Nothing blocking.

Description
Windows Sandbox can leave an error-only viewer beside its real desktop. Previously,
winapp target snapshot sandbox --jsoncould reportsandbox_target_ambiguousfor that pair, or report an error window alone as a rendered desktop.Recognized terminal-error windows no longer count as usable desktops. Unknown, starting, inaccessible, or unfamiliar windows remain conservative: a remembered viewer cannot bypass ambiguity, including when another window appears during restoration. Vanished identities are discarded; replacement windows retain fresh identities without inheriting ownership.
Access-denied and invalid-window inspection failures now report an unavailable desktop in structured JSON rather than escaping as an unexpected error. A missing native root is rejected before dereferencing it. The exception handling stays narrow and does not silently swallow unrelated programming errors.
Detection is read-only. It does not close viewers, reconnect, start/stop a guest, or repair ownership state. The classifier uses bounded, nonlocalized native/UI Automation structure rather than English error messages.
Usage Example
winapp target snapshot sandbox --jsonObserved with one real desktop and one singleton-error dialog: the installed baseline reported
sandbox_target_ambiguous; an earlier candidate reported the real desktop as rendered, adopted, nonminimized, and input/capture-ready without changing either viewer or the ownership record. If the selected desktop cannot be verified, readiness remains unavailable.Related Issue
Closes #887
Type of Change
Checklist
Screenshots / Demo
Textual CLI behavior above is the relevant evidence; no UI redesign or new screenshots are required. Tests use scripted viewer states and do not create or close live Sandbox windows.
Validation and limitations
-SkipTestsafter focused tests to avoid unauthorized UI activity on the shared desktop.Additional Notes
The caller responsible for the observed viewer process with
--helpremains unidentified; its argv does not prove direct invocation rather than a wrapper. This PR does not redesign cross-installation ownership, change Sandbox lifecycle, or claim command-performance improvements. The readiness label reflects technical checks, not human approval or permission to merge.