tailscale/tailscale · error

failed to set up second tailnet clients: %w

Error message

failed to set up second tailnet clients: %w

What it means

This error wraps failures from prodTailnetClientFromEnv when setting up API clients for the second test tailnet in the k8s-operator e2e harness. It is thrown by runTests (invoked from TestMain) when the second tailnet's credentials cannot be used to authenticate a Tailscale API client and operator credentials. It aborts the entire e2e test run before any tests execute.

Source

Thrown at cmd/k8s-operator/e2e/setup.go:388

			return 0, fmt.Errorf("failed to create OAuth client for second tailnet: %w", err)
		}
		secondOperatorCreds = oauthCreds{
			clientID:     secondKey.ID,
			clientSecret: secondKey.Key,
		}

		secondTSClient = tailscaleClientFromSecret("http://localhost:31544", secondOperatorCreds.clientID, secondOperatorCreds.clientSecret)
	} else {
		if !testCAs.AppendCertsFromPEM(leStagingRootX1) {
			return 0, fmt.Errorf("failed to parse Let's Encrypt staging root X1 cert")
		}
		tsClient, operatorCreds, err = prodTailnetClientFromEnv("TS_API_CLIENT_SECRET", "TS_API_CLIENT_ID", "TS_OPERATOR_CLIENT_ID")
		if err != nil {
			return 0, fmt.Errorf("failed to set up first tailnet clients: %w", err)
		}
		secondTSClient, secondOperatorCreds, err = prodTailnetClientFromEnv("SECOND_TS_API_CLIENT_SECRET", "SECOND_TS_API_CLIENT_ID", "SECOND_TS_OPERATOR_CLIENT_ID")
		if err != nil {
			return 0, fmt.Errorf("failed to set up second tailnet clients: %w", err)
		}
	}

	// Publish the trustedCAs as a ConfigMap that can be used by in-cluster
	// testing workloads.
	testCAPEM := leStagingRootX1
	if *fDevcontrol {
		testCAPEM = bytes.Join([][]byte{pebbleMiniCACert, pebbleCAChain}, []byte("\n"))
	}
	caCM := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{Name: testCAsConfigMap, Namespace: ns},
		Data:       map[string]string{testCAsConfigMapKey: string(testCAPEM)},
	}
	if err := createOrUpdate(ctx, kubeClient, caCM); err != nil {
		return 0, fmt.Errorf("failed to publish test CAs ConfigMap: %w", err)
	}
	defer kubeClient.Delete(context.Background(), caCM)

View on GitHub (pinned to e2ed432399)

Solutions

  1. Export SECOND_TS_API_CLIENT_SECRET, SECOND_TS_API_CLIENT_ID and SECOND_TS_OPERATOR_CLIENT_ID with valid values from the second tailnet's admin console
  2. Regenerate the OAuth clients for the second tailnet if credentials were rotated or revoked
  3. Verify all three credentials belong to the same second tailnet
  4. Skip second-tailnet tests if the harness supports a single-tailnet mode

Example fix

// before
$ ./run-e2e.sh
// after
$ export SECOND_TS_API_CLIENT_ID=k1234567890abcdef
$ export SECOND_TS_API_CLIENT_SECRET=tskey-client-...
$ export SECOND_TS_OPERATOR_CLIENT_ID=k0987654321fedcba
$ ./run-e2e.sh
Defensive patterns

Strategy: validation

Validate before calling

for _, k := range []string{"SECOND_TS_API_CLIENT_SECRET","SECOND_TS_API_CLIENT_ID","SECOND_TS_OPERATOR_CLIENT_ID"} {
  if os.Getenv(k) == "" { return fmt.Errorf("missing env var %s", k) }
}

Try / catch

if _, _, err := prodTailnetClientFromEnv(...); err != nil {
  var cfgErr *MissingEnvError
  if errors.As(err, &cfgErr) { log.Fatalf("set %s before running e2e", cfgErr.Key) }
  return err
}

Prevention

When it happens

Trigger: Calling prodTailnetClientFromEnv("SECOND_TS_API_CLIENT_SECRET", "SECOND_TS_API_CLIENT_ID", "SECOND_TS_OPERATOR_CLIENT_ID") fails — the env vars are unset/empty or the client secret/ID pair is rejected by the Tailscale API.

Common situations: CI or local runs against the multi-tailnet test setup where SECOND_TS_* env vars were never exported, the second tailnet's OAuth clients were revoked or expired, or the operator client ID belongs to a different tailnet than the API client.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of tailscale/tailscale@e2ed432399 (2026-09-14). Data as JSON: /api/errors/44af8e93a055bbf8. Report an issue: GitHub.