juanfont/headscale · error

expected %d clients, got %d

Error message

expected %d clients, got %d

What it means

Emitted from the retry lambda in Scenario.AddAndLoginClient when the number of clients returned by ListTailscaleClients does not equal the pre-creation count plus one. It is an assertion-style error (nolint:err113) used as a retry signal: the newly created container node has not registered with headscale yet, so the count check fails and backoff.Retry re-invokes the lambda after 500ms.

Source

Thrown at integration/helpers.go:1173

	}

	// Create the new node
	err = s.CreateTailscaleNodesInUser(username, version, 1, tsOpts...)
	if err != nil {
		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)
	}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the new tailscale container's logs (docker logs) to see if tailscaled failed to register or retried in a loop
  2. Check hs-*.stderr.log for registration errors such as TLS or noise handshake failures
  3. Free Docker resources or raise backoff.WithMaxElapsedTime if the host is consistently slow
  4. Verify the tailscale image version is compatible with the headscale version under test
Defensive patterns

Strategy: retry

Prevention

When it happens

Trigger: Scenario.CreateTailscaleNodesInUser started a new tailscale container, but the node has not completed registration with the control server yet (or never will because its container is broken). Every retry sees len(updatedClients) != len(originalClients)+1 for 10 seconds, then the error propagates wrapped as 'timeout waiting for new client'.

Common situations: Slow or overloaded Docker host so tailscaled takes >10s to register; wrong tailscale version image that cannot talk to the headscale API; TLS mismatch so the node registration silently fails; headscale server rejecting registrations due to policy config.

Related errors


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