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

  1. Ensure AddAndLoginClient is not called concurrently for the same username/scenario
  2. Read FindNewClient's matching logic in integration/helpers.go to see which field (hostname/index) failed to match
  3. Use unique usernames per test to avoid cross-test node collisions
  4. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/a055304be88690a1. Report an issue: GitHub.