{"record":{"id":"754a15c92a0b3694","repo":"juanfont/headscale","slug":"timeout-waiting-for-new-client-w","errorCode":null,"errorMessage":"timeout waiting for new client: %w","messagePattern":"timeout waiting for new client: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"integration/helpers.go","lineNumber":1184,"sourceCode":"\t_, err = backoff.Retry(t.Context(), func() (struct{}, error) {\n\t\tupdatedClients, err := s.ListTailscaleClients(username)\n\t\tif err != nil {\n\t\t\treturn struct{}{}, fmt.Errorf(\"listing updated clients: %w\", err)\n\t\t}\n\n\t\tif len(updatedClients) != len(originalClients)+1 {\n\t\t\treturn struct{}{}, fmt.Errorf(\"expected %d clients, got %d\", len(originalClients)+1, len(updatedClients)) //nolint:err113\n\t\t}\n\n\t\tnewClient, err = FindNewClient(originalClients, updatedClients)\n\t\tif err != nil {\n\t\t\treturn struct{}{}, fmt.Errorf(\"finding new client: %w\", err)\n\t\t}\n\n\t\treturn struct{}{}, nil\n\t}, backoff.WithBackOff(backoff.NewConstantBackOff(500*time.Millisecond)), backoff.WithMaxElapsedTime(10*time.Second))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"timeout waiting for new client: %w\", err)\n\t}\n\n\t// Get the user and create preauth key\n\tuser, err := GetUserByName(headscale, username)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user: %w\", err)\n\t}\n\n\tauthKey, err := s.CreatePreAuthKey(mustParseID(user.Id), true, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating preauth key: %w\", err)\n\t}\n\n\t// Login the new client\n\terr = newClient.Login(headscale.GetEndpoint(), authKey.Key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"logging in new client: %w\", err)\n\t}","sourceCodeStart":1166,"sourceCodeEnd":1202,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/integration/helpers.go#L1166-L1202","documentation":"Final wrapping error from Scenario.AddAndLoginClient when backoff.Retry exhausts its 10s budget (constant 500ms backoff) without the new client appearing. The %w chain preserves whichever sub-error (list failure, count mismatch, or FindNewClient failure) occurred on the last attempt, making it the umbrella error for 'new node never showed up'.","triggerScenarios":"Any of errors 660/661/662 persisting for the full backoff.WithMaxElapsedTime(10*time.Second) window: node never registers, listing always fails, or the diff never resolves to exactly one new client.","commonSituations":"Overloaded CI runner where container startup plus registration exceeds 10s; headscale container unhealthy so registration always fails; incompatible tailscale/headscale version pairing.","solutions":["Inspect the wrapped cause in the error chain — it names the actual failing step (listing, count, or find)","Check both headscale and the new tailscale container logs under control_logs/<runID>/","On consistently slow hosts, increase backoff.WithMaxElapsedTime in integration/helpers.go","Run `go run ./cmd/hi doctor` to rule out environment problems"],"exampleFix":"// before\n_, backoff.WithMaxElapsedTime(10*time.Second))\n// after (slow CI host)\n_, backoff.WithMaxElapsedTime(30*time.Second))","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Unwrap to find the real cause\nclient, err := scenario.AddAndLoginClient(t, user, ver)\nif err != nil {\n    var to *backoff.Error // or inspect err chain text\n    _ = to\n    t.Fatalf(\"add+login failed: %v\", err)\n}","preventionTips":["On slow CI, raise backoff.WithMaxElapsedTime in integration/helpers.go","Read the wrapped cause — it distinguishes listing vs count vs diff failures","Check both headscale and node container logs before assuming a code bug"],"tags":["integration-test","timeout","retry","backoff"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}