tailscale/tailscale · error
failed to create OAuth client for second tailnet: %w
Error message
failed to create OAuth client for second tailnet: %w
What it means
bootstrapClient.Keys().CreateOAuthClient failed creating the operator's OAuth client on the second tailnet, with the same request shape as the first tailnet (scopes auth_keys, devices:core, services; tag:k8s-operator). This is the credential that ends up in the second-tailnet-credentials Secret used by the multi-tailnet operator tests. Failure means devcontrol returned an error: the tag is not permitted (the second tailnet's policy from the previous step lacks tagOwners for tag:k8s-operator), a scope was rejected, the bootstrap token is invalid or expired, or the endpoint is unsupported for org-created tailnets in the running devcontrol build.
Source
Thrown at cmd/k8s-operator/e2e/setup.go:357
}
logger.Info("HTTPS settings configured for second tailnet")
// Set ACLs for second tailnet.
if err = bootstrapClient.PolicyFile().Set(ctx, string(requiredACLs), ""); err != nil {
return 0, fmt.Errorf("failed to set policy file: %w", err)
}
logger.Info("ACLs configured for second tailnet")
// Create an OAuth client for the second tailnet to be used
// by the k8s-operator.
secondKey, err := bootstrapClient.Keys().CreateOAuthClient(ctx, tailscale.CreateOAuthClientRequest{
Scopes: []string{"auth_keys", "devices:core", "services"},
Tags: []string{"tag:k8s-operator"},
Description: "k8s-operator client for e2e tests",
})
if err != nil {
return 0, fmt.Errorf("failed to create OAuth client for second tailnet: %w", err)
}
secondClientID = secondKey.ID
secondClientSecret = secondKey.Key
secondTSClient, err = tailscaleClientFromSecret(ctx, "http://localhost:31544", secondClientID, secondClientSecret)
if err != nil {
return 0, fmt.Errorf("failed to set up second tailnet client: %w", err)
}
} else {
if ok := testCAs.AppendCertsFromPEM(leStagingRootX1); !ok {
return 0, fmt.Errorf("failed to parse Let's Encrypt staging root")
}
clientSecret = os.Getenv("TS_API_CLIENT_SECRET")
if clientSecret == "" {
return 0, fmt.Errorf("must use --devcontrol or set TS_API_CLIENT_SECRET to an OAuth client suitable for the operator")
}View on GitHub (pinned to cfe32b8be6)
Solutions
- Verify the previous log line 'ACLs configured for second tailnet' appeared; if not, investigate the policy step first.
- Check devcontrol logs or the wrapped HTTP body to see whether a tag or a scope was rejected.
- Rebuild and restart devcontrol from the same commit as the tests; rerun the suite.
- Retry the run once for transient errors; the whole flow re-executes deterministically.
Defensive patterns
Strategy: retry
Validate before calling
// Assert policy is in place on the second tailnet before requesting tagged OAuth clients: // the ACL from the previous step must define tagOwners for tag:k8s-operator, otherwise this call fails with 400.
Try / catch
// Go: one bounded retry for 5xx/transport; keep 4xx fatal with the body.
secondKey, err := bootstrapClient.Keys().CreateOAuthClient(ctx, req)
if err != nil && isTransient(err) {
time.Sleep(2 * time.Second)
secondKey, err = bootstrapClient.Keys().CreateOAuthClient(ctx, req)
}
if err != nil {
return 0, fmt.Errorf("failed to create OAuth client for second tailnet: %w", err)
} Prevention
- Check that the 'ACLs configured for second tailnet' log line appears before this call in every run.
- Never change the tags or scopes in the harness request without updating acl.hujson tagOwners to match.
- Pin devcontrol and harness commits together in CI.
When it happens
Trigger: The second tailnet's ACL apply in the previous step did not take effect, so tag:k8s-operator has no owner and the server rejects the tag grant; the bootstrap token expired between steps; the devcontrol version does not support OAuth client creation scoped to an org tailnet; a transient 5xx from devcontrol.
Common situations: Editing requiredACLs without keeping tagOwners in sync; version skew between devcontrol and the harness; flaky devcontrol restarts in CI.
Related errors
- failed to create OAuth client for first tailnet: %w
- failed to set up second tailnet client: %w
- failed to set policy file: %w
- failed to create second tailnet: %w
- failed to configure https for second tailnet: %w
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/26ff67bee10d3221.
Report an issue: GitHub.