tailscale/tailscale · error
unexpected state from WaitForSingleObject: %d
Error message
unexpected state from WaitForSingleObject: %d
What it means
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.
Source
Thrown at util/winutil/restartmgr_windows.go:765
}
pi, err := startProcessInSessionInternal(sessID, cmdLineInfo, 0)
if err != nil {
return 1, err
}
windows.CloseHandle(pi.Thread)
defer windows.CloseHandle(pi.Process)
waitCode, err := windows.WaitForSingleObject(pi.Process, timeoutMillis)
if err != nil {
return 1, fmt.Errorf("WaitForSingleObject: %w", err)
}
if e := windows.Errno(waitCode); e == windows.WAIT_TIMEOUT {
return 1, e
}
if waitCode != windows.WAIT_OBJECT_0 {
// This should not be possible; log
return 1, fmt.Errorf("unexpected state from WaitForSingleObject: %d", waitCode)
}
var exitCode uint32
if err := windows.GetExitCodeProcess(pi.Process, &exitCode); err != nil {
return 1, err
}
return exitCode, nil
}
func startProcessInSessionInternal(sessID SessionID, cmdLineInfo CommandLineInfo, extraFlags uint32) (*windows.ProcessInformation, error) {
if err := cmdLineInfo.Validate(); err != nil {
return nil, err
}
token, err := sessID.OpenToken()
if err != nil {
return nil, fmt.Errorf("(*SessionID).OpenToken: %w", err)
}View on GitHub (pinned to 6e0912f979)
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
Defensive patterns
Strategy: try-catch
Try / catch
exitCode, err := winutil.RunProcessInSession(sessID, cli, timeout)
if err != nil && strings.Contains(err.Error(), "unexpected state from WaitForSingleObject") {
// defensive branch: log waitCode and retry once
logf("wait anomaly, retrying once: %v", err)
exitCode, err = winutil.RunProcessInSession(sessID, cli, timeout)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Machines with AV/EDR products that intercept process APIs; application-verifier or compatibility shim layers installed; rarely, driver bugs.
Related errors
- WaitForSingleObject: %w
- %w: expected samName to contain a backslash
- SetICMPErrImmunity: getting SyscallConn failed: %v
- SetICMPErrImmunity: could not set SIO_UDP_NETRESET: %v
- SetICMPErrImmunity: SyscallConn.Control failed: %v
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/5d71d7c03881338a.
Report an issue: GitHub.