{"record":{"id":"2535bd7cf5e572a6","repo":"microsoft/aspire","slug":"waitforsingleobject-failed-while-reading-isolatedprocess","errorCode":null,"errorMessage":"WaitForSingleObject failed while reading IsolatedProcess.HasExited","messagePattern":"WaitForSingleObject failed while reading IsolatedProcess\\.HasExited","errorType":"exception","errorClass":"Win32Exception","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Processes/IsolatedProcess.Windows.cs","lineNumber":275,"sourceCode":"            {\n                try { WindowsProcessInterop.CloseHandle(pi.hThread); } catch { }\n            }\n            processHandle?.Dispose();\n            throw;\n        }\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    private static bool GetHasExited(SafeProcessHandle processHandle)\n    {\n        ThrowIfDisposed(processHandle, nameof(HasExited));\n\n        var waitResult = WindowsProcessInterop.WaitForSingleObject(processHandle, 0);\n        return waitResult switch\n        {\n            WindowsProcessInterop.WaitObject0 => true,\n            WindowsProcessInterop.WaitTimeout => false,\n            WindowsProcessInterop.WaitFailed => throw new Win32Exception(Marshal.GetLastWin32Error(), \"WaitForSingleObject failed while reading IsolatedProcess.HasExited\"),\n            _ => throw new InvalidOperationException($\"Unexpected WaitForSingleObject result: 0x{waitResult:X8}\"),\n        };\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    private static int GetExitCode(SafeProcessHandle processHandle)\n    {\n        ThrowIfDisposed(processHandle, nameof(ExitCode));\n\n        // Disambiguate STILL_ACTIVE (259) from a real 259 exit code via a zero-timeout wait.\n        var waitResult = WindowsProcessInterop.WaitForSingleObject(processHandle, 0);\n        if (waitResult == WindowsProcessInterop.WaitTimeout)\n        {\n            throw new InvalidOperationException(\"Process has not exited; cannot read ExitCode.\");\n        }\n        if (waitResult == WindowsProcessInterop.WaitFailed)\n        {\n            throw new Win32Exception(Marshal.GetLastWin32Error(), \"WaitForSingleObject failed while reading IsolatedProcess.ExitCode\");","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Processes/IsolatedProcess.Windows.cs#L257-L293","documentation":"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.","triggerScenarios":"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.","commonSituations":"Querying HasExited after disposing the IsolatedProcess, a double-dispose race, or a corrupted/stale handle obtained in a broken environment.","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."],"exampleFix":"// before\nisolatedProcess.Dispose();\nif (isolatedProcess.HasExited) { ... }\n// after\nif (isolatedProcess.HasExited) { ... }\nisolatedProcess.Dispose();","handlingStrategy":"try-catch","validationCode":"// Check the object isn't disposed before polling\nif (startedProcess is IsolatedProcess ip && ip is IDisposable d)\n{\n    // track disposal with your own flag:\n    bool disposed = false; // set true in your Dispose path\n    if (disposed) throw new InvalidOperationException(\"Cannot poll HasExited after disposal.\");\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    bool exited = startedProcess.HasExited;\n}\ncatch (Win32Exception ex) when (ex.Message.Contains(\"WaitForSingleObject failed while reading IsolatedProcess.HasExited\"))\n{\n    // Handle was invalid — treat as 'unknown state'; re-check lifecycle/dispose ordering\n}","preventionTips":["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."],"tags":["windows","process","win32","process-lifetime"],"backgroundTag":"invalid-state-transition","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T09:17:21.228Z"}