juanfont/headscale · warning

executing command, expected string %q not found in %q

Error message

executing command, expected string %q not found in %q

What it means

Produced by assertCommandOutputContains when the command executes successfully but its stdout does not contain the expected substring. It is retried with exponential backoff for up to 10 seconds (eventual consistency for DNS/route/policy propagation), then surfaces as a test failure via assert.NoError.

Source

Thrown at integration/helpers.go:912

// assertCommandOutputContains executes a command with exponential backoff retry until the output
// contains the expected string or timeout is reached (10 seconds).
// This implements eventual consistency patterns and should be used instead of [time.Sleep]
// before executing commands that depend on network state propagation.
//
// Timeout: 10 seconds with exponential backoff
// Use cases: DNS resolution, route propagation, policy updates.
func assertCommandOutputContains(t *testing.T, c TailscaleClient, command []string, contains string) {
	t.Helper()

	_, err := backoff.Retry(t.Context(), func() (struct{}, error) {
		stdout, stderr, err := c.Execute(command)
		if err != nil {
			return struct{}{}, fmt.Errorf("executing command, stdout: %q stderr: %q, err: %w", stdout, stderr, err)
		}

		if !strings.Contains(stdout, contains) {
			return struct{}{}, fmt.Errorf("executing command, expected string %q not found in %q", contains, stdout) //nolint:err113
		}

		return struct{}{}, nil
	}, backoff.WithBackOff(backoff.NewExponentialBackOff()), backoff.WithMaxElapsedTime(10*time.Second))

	assert.NoError(t, err)
}

// dockertestMaxWait returns the maximum wait time for Docker-based test operations.
// Uses longer timeouts in CI environments to account for slower resource allocation
// and higher system load during automated testing.
func dockertestMaxWait() time.Duration {
	wait := 300 * time.Second //nolint

	if util.IsCI() {
		wait = 600 * time.Second //nolint
	}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Compare the actual stdout shown in the failure against the expected string for typos/formatting differences
  2. Verify the precondition (route enabled, policy applied, peer registered) actually holds before asserting
  3. If propagation is legitimately slow, split the wait from the assertion or assert on a more stable marker
Defensive patterns

Strategy: retry

Validate before calling

// cheap pre-check: run the command and inspect output yourself when debugging
stdout, _, err := c.Execute(command)
if err == nil && strings.Contains(stdout, contains) {
    // safe to assert; otherwise let the helper retry
}

Prevention

When it happens

Trigger: Calling assertCommandOutputContains(t, client, []string{"tailscale", "status"}, "peer-name") when the expected string hasn't propagated yet or is genuinely absent — wrong peer name, route not enabled, policy blocking visibility.

Common situations: Assertions made with a wrong expected string (typo in node name); state that takes longer than the 10 s budget to converge on slow CI; tests that assume immediate consistency after policy changes.

Related errors


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