{"record":{"id":"33e19075cf30bb86","repo":"microsoft/aspire","slug":"waitforsingleobject-failed-while-reading-isolatedprocess-33e190","errorCode":null,"errorMessage":"WaitForSingleObject failed while reading IsolatedProcess.ExitCode","messagePattern":"WaitForSingleObject failed while reading IsolatedProcess\\.ExitCode","errorType":"exception","errorClass":"Win32Exception","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Processes/IsolatedProcess.Windows.cs","lineNumber":293,"sourceCode":"            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\");\n        }\n\n        if (!WindowsProcessInterop.GetExitCodeProcess(processHandle, out var exitCode))\n        {\n            throw new Win32Exception(Marshal.GetLastWin32Error(), \"GetExitCodeProcess failed while reading IsolatedProcess.ExitCode\");\n        }\n\n        return unchecked((int)exitCode);\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    private static Task WaitForProcessHandleExitAsync(SafeProcessHandle processHandle, CancellationToken cancellationToken)\n    {\n        ThrowIfDisposed(processHandle, nameof(WaitForExitAsync));\n        return WindowsProcessInterop.WaitForExitAsync(processHandle, cancellationToken);\n    }\n\n    private static void ThrowIfDisposed(SafeProcessHandle processHandle, string memberName)","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Processes/IsolatedProcess.Windows.cs#L275-L311","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait process.WaitForExitAsync();\nprocess.Dispose();\nvar code = process.ExitCode;\n// after\nawait process.WaitForExitAsync();\nvar code = process.ExitCode;\nprocess.Dispose();","handlingStrategy":"try-catch","validationCode":"// Only read ExitCode after a completed wait, while the process is alive\nif (!startedProcess.HasExited)\n{\n    await startedProcess.WaitForExitAsync(cancellationToken);\n}\n// now safe to read ExitCode","typeGuard":null,"tryCatchPattern":"try\n{\n    int code = startedProcess.ExitCode;\n}\ncatch (Win32Exception ex) when (ex.Message.Contains(\"WaitForSingleObject failed while reading IsolatedProcess.ExitCode\"))\n{\n    // Invalid handle — lifecycle bug; treat as unknown exit code and fix dispose ordering\n}","preventionTips":["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."],"tags":["windows","process","win32","process-lifetime","exit-code"],"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"}