{"record":{"id":"26ff67bee10d3221","repo":"tailscale/tailscale","slug":"failed-to-create-oauth-client-for-second-tailnet","errorCode":null,"errorMessage":"failed to create OAuth client for second tailnet: %w","messagePattern":"failed to create OAuth client for second tailnet: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/k8s-operator/e2e/setup.go","lineNumber":357,"sourceCode":"\t\t}\n\t\tlogger.Info(\"HTTPS settings configured for second tailnet\")\n\n\t\t// Set ACLs for second tailnet.\n\t\tif err = bootstrapClient.PolicyFile().Set(ctx, string(requiredACLs), \"\"); err != nil {\n\t\t\treturn 0, fmt.Errorf(\"failed to set policy file: %w\", err)\n\t\t}\n\n\t\tlogger.Info(\"ACLs configured for second tailnet\")\n\n\t\t// Create an OAuth client for the second tailnet to be used\n\t\t// by the k8s-operator.\n\t\tsecondKey, err := bootstrapClient.Keys().CreateOAuthClient(ctx, tailscale.CreateOAuthClientRequest{\n\t\t\tScopes:      []string{\"auth_keys\", \"devices:core\", \"services\"},\n\t\t\tTags:        []string{\"tag:k8s-operator\"},\n\t\t\tDescription: \"k8s-operator client for e2e tests\",\n\t\t})\n\t\tif err != nil {\n\t\t\treturn 0, fmt.Errorf(\"failed to create OAuth client for second tailnet: %w\", err)\n\t\t}\n\t\tsecondClientID = secondKey.ID\n\t\tsecondClientSecret = secondKey.Key\n\n\t\tsecondTSClient, err = tailscaleClientFromSecret(ctx, \"http://localhost:31544\", secondClientID, secondClientSecret)\n\t\tif err != nil {\n\t\t\treturn 0, fmt.Errorf(\"failed to set up second tailnet client: %w\", err)\n\t\t}\n\n\t} else {\n\t\tif ok := testCAs.AppendCertsFromPEM(leStagingRootX1); !ok {\n\t\t\treturn 0, fmt.Errorf(\"failed to parse Let's Encrypt staging root\")\n\t\t}\n\n\t\tclientSecret = os.Getenv(\"TS_API_CLIENT_SECRET\")\n\t\tif clientSecret == \"\" {\n\t\t\treturn 0, fmt.Errorf(\"must use --devcontrol or set TS_API_CLIENT_SECRET to an OAuth client suitable for the operator\")\n\t\t}","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/k8s-operator/e2e/setup.go#L339-L375","documentation":"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.","triggerScenarios":"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.","commonSituations":"Editing requiredACLs without keeping tagOwners in sync; version skew between devcontrol and the harness; flaky devcontrol restarts in CI.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Assert policy is in place on the second tailnet before requesting tagged OAuth clients:\n// the ACL from the previous step must define tagOwners for tag:k8s-operator, otherwise this call fails with 400.","typeGuard":null,"tryCatchPattern":"// Go: one bounded retry for 5xx/transport; keep 4xx fatal with the body.\nsecondKey, err := bootstrapClient.Keys().CreateOAuthClient(ctx, req)\nif err != nil && isTransient(err) {\n    time.Sleep(2 * time.Second)\n    secondKey, err = bootstrapClient.Keys().CreateOAuthClient(ctx, req)\n}\nif err != nil {\n    return 0, fmt.Errorf(\"failed to create OAuth client for second tailnet: %w\", err)\n}","preventionTips":["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."],"tags":["go","oauth","devcontrol","api","e2e"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}