microsoft/aspire · error · Win32Exception
WaitForSingleObject failed while reading…
Error message
WaitForSingleObject failed while reading IsolatedProcess.HasExited
What it means
IsolatedProcess.HasExited on Windows is implemented by calling WaitForSingleObject(processHandle, 0) — a zero-timeout poll. WAIT_FAILED indicates the wait itself failed (usually because the process handle is no longer valid), and the code throws this Win32Exception with the underlying Win32 error instead of returning a possibly-wrong answer. WAIT_ABANDONED or other unexpected values hit the adjacent InvalidOperationException branch.
Solutions
- Do not read HasExited after the IsolatedProcess has been disposed; keep the instance alive for the duration of polling (wrap usage in try/finally or use await WaitForExitAsync instead).
- Check NativeErrorCode — ERROR_INVALID_HANDLE means the handle lifetime is the problem; audit dispose ordering in the caller.
- Prefer WaitForExitAsync / WaitForExit over repeated HasExited polling to avoid touching the raw handle after teardown.
- If polling is required, guard with ObjectDisposedException handling around the HasExited access.
- Update the Aspire CLI if the handle lifecycle is managed entirely by the library and the error still occurs.
Example fix
// before
isolatedProcess.Dispose();
if (isolatedProcess.HasExited) { ... }
// after
if (isolatedProcess.HasExited) { ... }
isolatedProcess.Dispose(); Defensive patterns
Strategy: try-catch
Validate before calling
// Check the object isn't disposed before polling
if (startedProcess is IsolatedProcess ip && ip is IDisposable d)
{
// track disposal with your own flag:
bool disposed = false; // set true in your Dispose path
if (disposed) throw new InvalidOperationException("Cannot poll HasExited after disposal.");
} Try / catch
try
{
bool exited = startedProcess.HasExited;
}
catch (Win32Exception ex) when (ex.Message.Contains("WaitForSingleObject failed while reading IsolatedProcess.HasExited"))
{
// Handle was invalid — treat as 'unknown state'; re-check lifecycle/dispose ordering
} Prevention
- Never access HasExited after disposing the IsolatedProcess; dispose only after you're done polling.
- Prefer await WaitForExitAsync() over manual HasExited polling loops.
- Keep exactly one owner responsible for the process lifetime to avoid double-dispose races.
- Capture final state (ExitCode) before teardown, not during it.
When it happens
Trigger: GetHasExited is invoked by HasExitedProvider from StartWindows or StartWindowsSuppressed while the underlying process handle has become invalid — typically after the SafeProcessHandle was disposed (IsolatedProcess disposed) or the handle was closed externally — and WaitForSingleObject returns WAIT_FAILED.
Common situations: Querying HasExited after disposing the IsolatedProcess, a double-dispose race, or a corrupted/stale handle obtained in a broken environment.
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
- 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
- Failed to set NUL handle inheritance
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/2535bd7cf5e572a6.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Cli/Processes/IsolatedProcess.Windows.cs:275
{
try { WindowsProcessInterop.CloseHandle(pi.hThread); } catch { }
}
processHandle?.Dispose();
throw;
}
}
[SupportedOSPlatform("windows")]
private static bool GetHasExited(SafeProcessHandle processHandle)
{
ThrowIfDisposed(processHandle, nameof(HasExited));
var waitResult = WindowsProcessInterop.WaitForSingleObject(processHandle, 0);
return waitResult switch
{
WindowsProcessInterop.WaitObject0 => true,
WindowsProcessInterop.WaitTimeout => false,
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");View on GitHub (pinned to 25830f84bd)