juanfont/headscale · error
finding new client: %w
Error message
finding new client: %w
What it means
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.
Source
Thrown at integration/helpers.go:1178
return nil, fmt.Errorf("creating tailscale node: %w", err)
}
// Wait for the new node to appear in the client list
var newClient TailscaleClient
_, err = backoff.Retry(t.Context(), func() (struct{}, error) {
updatedClients, err := s.ListTailscaleClients(username)
if err != nil {
return struct{}{}, fmt.Errorf("listing updated clients: %w", err)
}
if len(updatedClients) != len(originalClients)+1 {
return struct{}{}, fmt.Errorf("expected %d clients, got %d", len(originalClients)+1, len(updatedClients)) //nolint:err113
}
newClient, err = FindNewClient(originalClients, updatedClients)
if err != nil {
return struct{}{}, fmt.Errorf("finding new client: %w", err)
}
return struct{}{}, nil
}, backoff.WithBackOff(backoff.NewConstantBackOff(500*time.Millisecond)), backoff.WithMaxElapsedTime(10*time.Second))
if err != nil {
return nil, fmt.Errorf("timeout waiting for new client: %w", err)
}
// Get the user and create preauth key
user, err := GetUserByName(headscale, username)
if err != nil {
return nil, fmt.Errorf("getting user: %w", err)
}
authKey, err := s.CreatePreAuthKey(mustParseID(user.Id), true, false)
if err != nil {
return nil, fmt.Errorf("creating preauth key: %w", err)
}View on GitHub (pinned to 565fd254d0)
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
Defensive patterns
Strategy: validation
Validate before calling
// Snapshot immediately before creating the node, and never mutate the user concurrently
original, err := scenario.ListTailscaleClients(username)
if err != nil { t.Fatal(err) }
// ... create exactly one node, then diff Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- node no longer exists in NodeStore
- dumping config: %w
- checking name uniqueness: %w
- registering existing node in database: %w
- creating certificates for derp test: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/a055304be88690a1.
Report an issue: GitHub.