{"record":{"id":"705410d8373a9469","repo":"tailscale/tailscale","slug":"windows-waitforsingleobject-w","errorCode":null,"errorMessage":"windows.WaitForSingleObject: %w","messagePattern":"windows\\.WaitForSingleObject: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/winutil/winutil_windows.go","lineNumber":553,"sourceCode":"\t\t\tif err != nil {\n\t\t\t\treturn 0, fmt.Errorf(\"windows.RegNotifyChangeKeyValue: %w\", err)\n\t\t\t}\n\n\t\t\tvar accessFlags uint32\n\t\t\tif isLast {\n\t\t\t\taccessFlags = access\n\t\t\t} else {\n\t\t\t\taccessFlags = registry.NOTIFY\n\t\t\t}\n\t\t\tkey, err = registry.OpenKey(k, keyName, accessFlags)\n\t\t\tif err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND {\n\t\t\t\ttimeout := time.Until(deadline) / time.Millisecond\n\t\t\t\tif timeout < 0 {\n\t\t\t\t\ttimeout = 0\n\t\t\t\t}\n\t\t\t\ts, err := windows.WaitForSingleObject(event, uint32(timeout))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, fmt.Errorf(\"windows.WaitForSingleObject: %w\", err)\n\t\t\t\t}\n\t\t\t\tif s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows\n\t\t\t\t\treturn 0, ErrKeyWaitTimeout\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\treturn 0, fmt.Errorf(\"registry.OpenKey(%v): %w\", path, err)\n\t\t\t} else {\n\t\t\t\tif isLast {\n\t\t\t\t\treturn key, nil\n\t\t\t\t}\n\t\t\t\tdefer key.Close()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tk = key\n\t}\n}","sourceCodeStart":535,"sourceCodeEnd":571,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/util/winutil/winutil_windows.go#L535-L571","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"key, err := winutil.OpenKeyWait(base, path, access)\nif err != nil {\n\tif errors.Is(err, winutil.ErrKeyWaitTimeout) {\n\t\t// expected: key did not appear within timeout\n\t} else if strings.Contains(err.Error(), \"WaitForSingleObject\") {\n\t\t// handle-lifetime bug; audit concurrent CloseHandle of wait events\n\t}\n}","preventionTips":["Distinguish ErrKeyWaitTimeout (expected) from API failure via errors.Is","Guarantee exclusive ownership of change events used by wait loops"],"tags":["windows","registry","wait","kernel-sync","handle-lifetime"],"backgroundTag":"event-wait-failed","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}