microsoft/aspire · error · Win32Exception

WaitForSingleObject failed while reading…

Error message

WaitForSingleObject failed while reading IsolatedProcess.ExitCode

What it means

Reading ExitCode on Windows first polls WaitForSingleObject(handle, 0) to confirm the child actually exited. WAIT_FAILED means the wait could not be performed — nearly always an invalid process handle — so the CLI throws this Win32Exception with the Win32 error code rather than returning a misleading exit code. A separate guard throws if the process is merely still running (WAIT_TIMEOUT).

Solutions

  1. Read ExitCode only while the IsolatedProcess is alive and only after WaitForExit/WaitForExitAsync has returned.
  2. Check NativeErrorCode: ERROR_INVALID_HANDLE (6) confirms a handle-lifetime bug — fix dispose ordering in the caller.
  3. Replace late ExitCode reads with capturing the value inside a WaitForExit continuation before disposal.
  4. Guard shutdown code with try/catch around ExitCode access and treat post-dispose reads as 'unknown exit'.
  5. Update the Aspire CLI if the error occurs in purely library-managed lifecycles.

Example fix

// before
await process.WaitForExitAsync();
process.Dispose();
var code = process.ExitCode;
// after
await process.WaitForExitAsync();
var code = process.ExitCode;
process.Dispose();
Defensive patterns

Strategy: try-catch

Validate before calling

// Only read ExitCode after a completed wait, while the process is alive
if (!startedProcess.HasExited)
{
    await startedProcess.WaitForExitAsync(cancellationToken);
}
// now safe to read ExitCode

Try / catch

try
{
    int code = startedProcess.ExitCode;
}
catch (Win32Exception ex) when (ex.Message.Contains("WaitForSingleObject failed while reading IsolatedProcess.ExitCode"))
{
    // Invalid handle — lifecycle bug; treat as unknown exit code and fix dispose ordering
}

Prevention

When it happens

Trigger: GetExitCode, invoked via ExitCodeProvider from StartWindows/StartWindowsSuppressed, sees waitResult == WAIT_FAILED because the process handle is invalid — e.g. the IsolatedProcess was disposed before reading ExitCode, or the handle was corrupted externally.

Common situations: Accessing ExitCode after Dispose, double-dispose races in shutdown paths, or library misuse where the exit code is read during teardown after the SafeProcessHandle finalizer ran.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/33e19075cf30bb86. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Cli/Processes/IsolatedProcess.Windows.cs:293

            WindowsProcessInterop.WaitFailed => throw new Win32Exception(Marshal.GetLastWin32Error(), "WaitForSingleObject failed while reading IsolatedProcess.HasExited"),
            _ => throw new InvalidOperationException($"Unexpected WaitForSingleObject result: 0x{waitResult:X8}"),
        };
    }

    [SupportedOSPlatform("windows")]
    private static int GetExitCode(SafeProcessHandle processHandle)
    {
        ThrowIfDisposed(processHandle, nameof(ExitCode));

        // Disambiguate STILL_ACTIVE (259) from a real 259 exit code via a zero-timeout wait.
        var waitResult = WindowsProcessInterop.WaitForSingleObject(processHandle, 0);
        if (waitResult == WindowsProcessInterop.WaitTimeout)
        {
            throw new InvalidOperationException("Process has not exited; cannot read ExitCode.");
        }
        if (waitResult == WindowsProcessInterop.WaitFailed)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "WaitForSingleObject failed while reading IsolatedProcess.ExitCode");
        }

        if (!WindowsProcessInterop.GetExitCodeProcess(processHandle, out var exitCode))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "GetExitCodeProcess failed while reading IsolatedProcess.ExitCode");
        }

        return unchecked((int)exitCode);
    }

    [SupportedOSPlatform("windows")]
    private static Task WaitForProcessHandleExitAsync(SafeProcessHandle processHandle, CancellationToken cancellationToken)
    {
        ThrowIfDisposed(processHandle, nameof(WaitForExitAsync));
        return WindowsProcessInterop.WaitForExitAsync(processHandle, cancellationToken);
    }

    private static void ThrowIfDisposed(SafeProcessHandle processHandle, string memberName)

View on GitHub (pinned to 25830f84bd)