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
- Read ExitCode only while the IsolatedProcess is alive and only after WaitForExit/WaitForExitAsync has returned.
- Check NativeErrorCode: ERROR_INVALID_HANDLE (6) confirms a handle-lifetime bug — fix dispose ordering in the caller.
- Replace late ExitCode reads with capturing the value inside a WaitForExit continuation before disposal.
- Guard shutdown code with try/catch around ExitCode access and treat post-dispose reads as 'unknown exit'.
- 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
- Wait for exit (WaitForExit/WaitForExitAsync) before reading ExitCode.
- Read ExitCode before disposing the IsolatedProcess.
- Centralize child-process teardown in one place to avoid double-dispose races.
- Treat exit-code reads during shutdown as optional and guard them.
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
- GetExitCodeProcess failed while reading…
- WaitForSingleObject failed while reading…
- Failed to create CLI kill-on-parent-exit job object
- Failed to open NUL device
- Failed to open NUL device for stdin
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)