Skip to content

IsolationSession: a second deprovision reports success instead of stale_id #1429

Description

@theelliotm

Relevant area(s)

Windows

Brief description of your issue

Calling deprovision twice on the same IsolationSession sandboxId succeeds both times. The second call should fail with error.code: "stale_id", because by then the agent user it is meant to remove is already gone.

Every other lifecycle operation gets this right. Running start, stop, or exec against a deprovisioned sandboxId correctly returns stale_id with nativeCode: "0x80070490". Only deprovision reports success.

This matters because stale_id is how a caller learns that a sandboxId is dead. A caller that retries a deprovision it is unsure about — which the docs actively recommend as a best-effort cleanup pattern — gets a success back and has no way to tell whether it cleaned anything up or operated on nothing at all.

Steps to reproduce

  1. provision an IsolationSession sandbox and keep the returned sandboxId.
  2. deprovision it. This succeeds.
  3. deprovision the same sandboxId again.

Covered by two scenarios in tests/scripts/run_isolation_session_state_aware_tests.ps1:

  • stale_id (deprovision on previously-deprovisioned sandbox)
  • Lifecycle G: repeating deprovision reports a stale sandbox

Both fail identically on all four isolation-session jobs (prerelease-26h1 and prerelease-isolation-session, x64 and arm64) in https://github.com/microsoft/mxc/actions/runs/37522853336, so this is deterministic rather than flaky.

Expected behavior

The second deprovision fails with a non-zero exit code and an error envelope carrying:

  • error.code: "stale_id"
  • error.operation naming the failing API call (IsoSessionOps.*)
  • error.nativeCode: "0x80070490" (HRESULT_FROM_WIN32(ERROR_NOT_FOUND))

This is the documented contract in two places:

  • docs/isolation-session/state-aware-rust.md:404 — "A second deprovision on the same sandboxId fails the OS-side agent-user lookup (HRESULT_FROM_WIN32(ERROR_NOT_FOUND)), which the runner maps to MxcError::StaleId."
  • docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md:1804 — "deprovision either succeeds (cleanup completes) or returns stale_id."

Actual behavior

The second deprovision exits 0 with an empty error:

FAIL: exit code is non-zero (deprovision on a stale sandbox failed as expected)
FAIL: error.code is 'stale_id' (got '')
FAIL: a second deprovision does not report success

The MXC-side plumbing looks correct, so the break appears to be below it. IsolationSessionBackend::deprovision (src/mxc-sdk/src/backends/isolation_session/common/state_aware.rs:171) calls straight through to deprovision_agent_user with no short-circuit, and that call already classifies with StalePromotion::Eligible (manager.rs:674-680). So the ERROR_NOT_FOUND → stale_id promotion is wired up and would fire — which suggests the underlying RemoveUser call is returning success for an agent user that no longer exists, leaving nothing to promote.

Worth confirming where the chain actually breaks before picking a fix, since the options differ a lot:

  • have MXC detect the missing agent user itself and raise stale_id,
  • get the OS API to report ERROR_NOT_FOUND (cross-repo), or
  • decide deprovision is idempotent on purpose and update the docs and tests to match.

Activity

  1. added
    Issue-BugSomething is not working as intended.
    Needs-TriageHas not yet been reviewed, classified, and routed.
    on Oct 6, 2026
  2. removed their assignment
    on Oct 6, 2026
  3. added
    Backend-IsolationSessionWindows IsolationSession backend behavior or integration.
    Priority2High-impact issue affecting key functionality; prioritize for the next appropriate release.
    and removed
    Needs-TriageHas not yet been reviewed, classified, and routed.
    on Oct 6, 2026
  4. impartshadow commented on Oct 10, 2026

    @impartshadow

    This looks like the classic success-on-noop class: the second call isn't idempotent-by-design, it's reporting success because the OS call it depends on is itself idempotent in a way MXC's error-mapping never anticipated.

    Your own tracing nails the location but stops one layer short. You note deprovision_agent_user "already classifies with StalePromotion::Eligible," which means the promotion path is correctly wired — it's just never triggered because RemoveUser returns success on a missing account instead of ERROR_NOT_FOUND. That's consistent with Windows account-management APIs that treat "already absent" as a satisfied postcondition rather than a failure — which is exactly why start, stop, and exec (which query for presence rather than request removal) correctly surface 0x80070490 while deprovision doesn't: different OS primitive, different error semantics, same promotion code sitting downstream of both.

    The check that catches this at the right boundary: at the IsolationSessionBackend::deprovision call site (state_aware.rs:171), before invoking deprovision_agent_user, do a presence check (or capture the pre-call existence state) and assert that a no-op removal path is never let through as Ok. Concretely: contract-test RemoveUser on a known-absent account in isolation, independent of the deprovision flow, and assert its return code — don't infer OS behavior from one integration test, verify it directly. If it returns success, the fix has to be in MXC (explicit existence check before/after calling the API), not in the OS layer.

    What would confirm vs. refute this: a standalone call to the underlying account-removal API against a nonexistent user, outside the deprovision flow — if it returns a success HRESULT, this is confirmed; if it returns ERROR_NOT_FOUND and MXC still swallows it, the break is in deprovision_agent_user's error handling instead, not the OS call.

    Advisory SFA-2026-E68A6B4000 · Full redacted write-up: https://impartshadow.github.io/echo-site/failure-cases/isolationsession-a-second-deprovision-reports-succes-e68a6b4000.html
    If this diagnosis is useful, react 👍 to this comment; if it is wrong, react 👎 — I use the verdict to choose what I investigate next.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Backend-IsolationSessionWindows IsolationSession backend behavior or integration.Issue-BugSomething is not working as intended.OS-WindowsApplies specifically to Windows hosts or Windows behavior.Priority2High-impact issue affecting key functionality; prioritize for the next appropriate release.

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions