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

  1. Ensure nothing closes the event handle while the wait is outstanding
  2. Audit for concurrent use of the same key/event state across goroutines
  3. 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

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


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/705410d8373a9469. Report an issue: GitHub.