{"record":{"id":"5d71d7c03881338a","repo":"tailscale/tailscale","slug":"unexpected-state-from-waitforsingleobject-d","errorCode":null,"errorMessage":"unexpected state from WaitForSingleObject: %d","messagePattern":"unexpected state from WaitForSingleObject: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/winutil/restartmgr_windows.go","lineNumber":765,"sourceCode":"\t}\n\n\tpi, err := startProcessInSessionInternal(sessID, cmdLineInfo, 0)\n\tif err != nil {\n\t\treturn 1, err\n\t}\n\twindows.CloseHandle(pi.Thread)\n\tdefer windows.CloseHandle(pi.Process)\n\n\twaitCode, err := windows.WaitForSingleObject(pi.Process, timeoutMillis)\n\tif err != nil {\n\t\treturn 1, fmt.Errorf(\"WaitForSingleObject: %w\", err)\n\t}\n\tif e := windows.Errno(waitCode); e == windows.WAIT_TIMEOUT {\n\t\treturn 1, e\n\t}\n\tif waitCode != windows.WAIT_OBJECT_0 {\n\t\t// This should not be possible; log\n\t\treturn 1, fmt.Errorf(\"unexpected state from WaitForSingleObject: %d\", waitCode)\n\t}\n\n\tvar exitCode uint32\n\tif err := windows.GetExitCodeProcess(pi.Process, &exitCode); err != nil {\n\t\treturn 1, err\n\t}\n\treturn exitCode, nil\n}\n\nfunc startProcessInSessionInternal(sessID SessionID, cmdLineInfo CommandLineInfo, extraFlags uint32) (*windows.ProcessInformation, error) {\n\tif err := cmdLineInfo.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\ttoken, err := sessID.OpenToken()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"(*SessionID).OpenToken: %w\", err)\n\t}","sourceCodeStart":747,"sourceCodeEnd":783,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/util/winutil/restartmgr_windows.go#L747-L783","documentation":"After WaitForSingleObject returns, RunProcessInSession expects exactly WAIT_OBJECT_0 (signaled) or WAIT_TIMEOUT; anything else lands here. The source comments say 'this should not be possible': process handles do not produce WAIT_ABANDONED, and WAIT_FAILED is normally surfaced as err. Encountering it means undocumented behavior or interference with the wait.","triggerScenarios":"Security software or shims hooking kernel32 WaitForSingleObject; WAIT_FAILED returned as a code instead of err by a hooked implementation; debugger-attached oddities altering wait semantics.","commonSituations":"Machines with AV/EDR products that intercept process APIs; application-verifier or compatibility shim layers installed; rarely, driver bugs.","solutions":["Log the waitCode value and retry the wait once with the remaining timeout","Reproduce under Process Monitor / with app-verifier disabled to identify the hook","If it recurs on specific machines, collect the waitCode and fall back to GetExitCodeProcess-based STILL_ACTIVE polling"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"exitCode, err := winutil.RunProcessInSession(sessID, cli, timeout)\nif err != nil && strings.Contains(err.Error(), \"unexpected state from WaitForSingleObject\") {\n    // defensive branch: log waitCode and retry once\n    logf(\"wait anomaly, retrying once: %v\", err)\n    exitCode, err = winutil.RunProcessInSession(sessID, cli, timeout)\n}","preventionTips":["Treat this branch as an environment anomaly: record the numeric waitCode","Check for AV/EDR or app-verifier hooks when it reproduces on specific hosts","Do not build logic on it - it is documented as unreachable in the source"],"tags":["go","windows","waitforsingleobject","defensive-check","unexpected-state"],"backgroundTag":"waitforsingleobject-failed","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}