{"record":{"id":"a055304be88690a1","repo":"juanfont/headscale","slug":"finding-new-client-w","errorCode":null,"errorMessage":"finding new client: %w","messagePattern":"finding new client: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"integration/helpers.go","lineNumber":1178,"sourceCode":"\t\treturn nil, fmt.Errorf(\"creating tailscale node: %w\", err)\n\t}\n\n\t// Wait for the new node to appear in the client list\n\tvar newClient TailscaleClient\n\n\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}","sourceCodeStart":1160,"sourceCodeEnd":1196,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/integration/helpers.go#L1160-L1196","documentation":"Produced when FindNewClient(originalClients, updatedClients) fails inside the AddAndLoginClient retry loop. FindNewClient diffs the old and new client lists to identify the single node added between snapshots; it errors when the diff is ambiguous (e.g. more than one new client, or matching failure by hostname/index). This indicates concurrent node churn rather than a single clean addition.","triggerScenarios":"Two AddAndLoginClient (or CreateTailscaleNodesInUser) calls racing in the same scenario/user, so more than one new client appears between snapshots; or the new node registered but its hostname/index cannot be matched against the original list (e.g. index-shift heuristics in FindNewClient fail).","commonSituations":"Tests that call t.Parallel() while sharing a Scenario; helper invoked concurrently for the same user; leftover nodes from a previous run with duplicate names.","solutions":["Ensure AddAndLoginClient is not called concurrently for the same username/scenario","Read FindNewClient's matching logic in integration/helpers.go to see which field (hostname/index) failed to match","Use unique usernames per test to avoid cross-test node collisions","Clean up stale containers with `go run ./cmd/hi cleanup` before re-running"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Snapshot immediately before creating the node, and never mutate the user concurrently\noriginal, err := scenario.ListTailscaleClients(username)\nif err != nil { t.Fatal(err) }\n// ... create exactly one node, then diff","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not call AddAndLoginClient concurrently for the same user/scenario","Use unique usernames per test to keep client lists disjoint","Prefer CreateTailscaleNodesInUser + explicit index handling when adding multiple nodes"],"tags":["integration-test","diffing","concurrency"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}