juanfont/headscale · error

timeout waiting for new client: %w

Error message

timeout waiting for new client: %w

What it means

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'.

Source

Thrown at integration/helpers.go:1184

	_, 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)
	}

	// Login the new client
	err = newClient.Login(headscale.GetEndpoint(), authKey.Key)
	if err != nil {
		return nil, fmt.Errorf("logging in new client: %w", err)
	}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Inspect the wrapped cause in the error chain — it names the actual failing step (listing, count, or find)
  2. Check both headscale and the new tailscale container logs under control_logs/<runID>/
  3. On consistently slow hosts, increase backoff.WithMaxElapsedTime in integration/helpers.go
  4. Run `go run ./cmd/hi doctor` to rule out environment problems

Example fix

// before
_, backoff.WithMaxElapsedTime(10*time.Second))
// after (slow CI host)
_, backoff.WithMaxElapsedTime(30*time.Second))
Defensive patterns

Strategy: retry

Try / catch

// Unwrap to find the real cause
client, err := scenario.AddAndLoginClient(t, user, ver)
if err != nil {
    var to *backoff.Error // or inspect err chain text
    _ = to
    t.Fatalf("add+login failed: %v", err)
}

Prevention

When it happens

Trigger: 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.

Common situations: Overloaded CI runner where container startup plus registration exceeds 10s; headscale container unhealthy so registration always fails; incompatible tailscale/headscale version pairing.

Understand the failure class

Related errors


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