tailscale/tailscale · error
windows.WaitForSingleObject: %w
Error message
windows.WaitForSingleObject: %w
What it means
When a registry key component is not yet present, OpenKeyWait blocks on the change event via WaitForSingleObject until the keyOpenTimeout deadline. The timeout case is deliberately mapped to ErrKeyWaitTimeout, so this error means the wait API itself failed — essentially only when the event handle is invalid.
Source
Thrown at util/winutil/winutil_windows.go:553
if err != nil {
return 0, fmt.Errorf("windows.RegNotifyChangeKeyValue: %w", err)
}
var accessFlags uint32
if isLast {
accessFlags = access
} else {
accessFlags = registry.NOTIFY
}
key, err = registry.OpenKey(k, keyName, accessFlags)
if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND {
timeout := time.Until(deadline) / time.Millisecond
if timeout < 0 {
timeout = 0
}
s, err := windows.WaitForSingleObject(event, uint32(timeout))
if err != nil {
return 0, fmt.Errorf("windows.WaitForSingleObject: %w", err)
}
if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows
return 0, ErrKeyWaitTimeout
}
} else if err != nil {
return 0, fmt.Errorf("registry.OpenKey(%v): %w", path, err)
} else {
if isLast {
return key, nil
}
defer key.Close()
break
}
}
k = key
}
}View on GitHub (pinned to 6e0912f979)
Solutions
- Ensure nothing closes the event handle while the wait is outstanding
- Audit for concurrent use of the same key/event state across goroutines
- Treat persistent occurrences as a bug in calling-code handle ownership, not a system fault to configure around
Defensive patterns
Strategy: retry
Try / catch
key, err := winutil.OpenKeyWait(base, path, access)
if err != nil {
if errors.Is(err, winutil.ErrKeyWaitTimeout) {
// expected: key did not appear within timeout
} else if strings.Contains(err.Error(), "WaitForSingleObject") {
// handle-lifetime bug; audit concurrent CloseHandle of wait events
}
} Prevention
- Distinguish ErrKeyWaitTimeout (expected) from API failure via errors.Is
- Guarantee exclusive ownership of change events used by wait loops
When it happens
Trigger: windows.WaitForSingleObject(event, timeout) returning an error: the event handle was closed or became invalid before/during the wait (note the per-component loop defers CloseHandle, so concurrent close is the realistic path), or a rare kernel failure.
Common situations: Concurrent code closing the registry change event while the wait is in flight; multiple OpenKeyWait calls racing on shared state; almost always a handle-lifetime bug in the caller rather than an environment problem.
Related errors
- windows.RegNotifyChangeKeyValue: %w
- opening %s: %w
- opening %q: %w
- failed to open the %s key: %w
- failed to get token user: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/705410d8373a9469.
Report an issue: GitHub.