{"record":{"id":"ca3439d47529c3b3","repo":"tailscale/tailscale","slug":"failed-to-get-tailscale-client-and-loginurl-w","errorCode":null,"errorMessage":"failed to get tailscale client and loginUrl: %w","messagePattern":"failed to get tailscale client and loginUrl: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/k8s-operator/proxygroup.go","lineNumber":137,"sourceCode":"\tlogger := r.logger(req.Name)\n\tlogger.Debugf(\"starting reconcile\")\n\tdefer logger.Debugf(\"reconcile finished\")\n\n\tpg := new(tsapi.ProxyGroup)\n\terr = r.Get(ctx, req.NamespacedName, pg)\n\tif apierrors.IsNotFound(err) {\n\t\tlogger.Debugf(\"ProxyGroup not found, assuming it was deleted\")\n\t\treturn reconcile.Result{}, nil\n\t} else if err != nil {\n\t\treturn reconcile.Result{}, fmt.Errorf(\"failed to get tailscale.com ProxyGroup: %w\", err)\n\t}\n\n\ttsClient, err := r.clients.For(pg.Spec.Tailnet)\n\tif err != nil {\n\t\toldPGStatus := pg.Status.DeepCopy()\n\t\tnrr := &notReadyReason{\n\t\t\treason:  reasonProxyGroupTailnetUnavailable,\n\t\t\tmessage: fmt.Errorf(\"failed to get tailscale client and loginUrl: %w\", err).Error(),\n\t\t}\n\n\t\treturn reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, make(map[string][]netip.AddrPort)))\n\t}\n\n\tif markedForDeletion(pg) {\n\t\tlogger.Debugf(\"ProxyGroup is being deleted, cleaning up resources\")\n\t\tix := xslices.Index(pg.Finalizers, FinalizerName)\n\t\tif ix < 0 {\n\t\t\tlogger.Debugf(\"no finalizer, nothing to do\")\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\n\t\tif done, err := r.maybeCleanup(ctx, tsClient, pg); err != nil {\n\t\t\tif strings.Contains(err.Error(), optimisticLockErrorMsg) {\n\t\t\t\tlogger.Infof(\"optimistic lock error, retrying: %s\", err)\n\t\t\t\treturn reconcile.Result{}, nil\n\t\t\t}","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/k8s-operator/proxygroup.go#L119-L155","documentation":"The operator supports multiple tailnets: ClientProvider.For(tailnet) returns a per-tailnet API client, failing with ErrClientNotFound for an unregistered tailnet or ErrNotReady for one whose login/API credentials are not yet usable. This error (also recorded on the ProxyGroup status with reason TailnetUnavailable) means the value in the ProxyGroup's spec.tailnet cannot be resolved to a usable client. Provisioning halts until the tailnet becomes available; the reconciler also attempts to update status, joining both errors.","triggerScenarios":"r.clients.For(pg.Spec.Tailnet) where (1) spec.tailnet names a tailnet never registered with the operator via its multi-tailnet configuration (ErrClientNotFound: <name>), (2) the tailnet is registered but not yet marked ready — e.g., its login server credentials/API key not yet validated (ErrNotReady: <name>), (3) spec.tailnet non-empty while the operator runs without multi-tailnet support configured, so only the default (blank) tailnet exists.","commonSituations":"Setting spec.tailnet on a ProxyGroup without first configuring that tailnet on the operator; typos in the tailnet name; the tailnet's credential Secret still being bootstrapped; feature-flag/multi-tailnet alpha config missing after operator restart.","solutions":["Check the ProxyGroup status conditions for TailnetUnavailable and the embedded reason (client not found vs tailnet not ready)","Verify spec.tailnet exactly matches a tailnet configured on the operator; fix typos or leave it blank to use the default tailnet","Complete the operator-side configuration for that tailnet (register its API client/login URL) and wait for readiness","If the tailnet is intentionally gone, update or delete the ProxyGroups referencing it"],"exampleFix":"# before\napiVersion: tailscale.com/v1alpha1\nkind: ProxyGroup\nmetadata:\n  name: pg\nspec:\n  tailnet: prod-tailent # typo, unregistered\n# after\napiVersion: tailscale.com/v1alpha1\nkind: ProxyGroup\nmetadata:\n  name: pg\nspec:\n  tailnet: prod-tailnet","handlingStrategy":"validation","validationCode":"// Before applying a ProxyGroup, verify the tailnet is resolvable\nif _, err := clients.For(pg.Spec.Tailnet); err != nil {\n\tif errors.Is(err, tsclient.ErrClientNotFound) {\n\t\treturn fmt.Errorf(\"tailnet %q not configured on operator\", pg.Spec.Tailnet)\n\t}\n\tif errors.Is(err, tsclient.ErrNotReady) {\n\t\treturn fmt.Errorf(\"tailnet %q not ready yet, retry\", pg.Spec.Tailnet)\n\t}\n}","typeGuard":"func tailnetUnavailable(err error) (notFound, notReady bool) {\n\tnotFound = errors.Is(err, tsclient.ErrClientNotFound)\n\tnotReady = errors.Is(err, tsclient.ErrNotReady)\n\treturn\n}","tryCatchPattern":"tsClient, err := r.clients.For(pg.Spec.Tailnet)\nif err != nil {\n\tif errors.Is(err, tsclient.ErrClientNotFound) {\n\t\t// deterministic misconfig: surface on status, stop hot-retrying\n\t\tnrr := &notReadyReason{reason: reasonProxyGroupTailnetUnavailable, message: err.Error()}\n\t\treturn reconcile.Result{}, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, nil)\n\t}\n\t// ErrNotReady is transient: requeue\n\treturn reconcile.Result{RequeueAfter: time.Minute}, nil\n}","preventionTips":["Configure and validate a tailnet on the operator before applying ProxyGroups that reference it","Leave spec.tailnet blank unless multi-tailnet is intentionally enabled","Watch the ProxyGroup TailnetUnavailable condition rather than logs to detect drift","Keep tailnet names in a single source of truth (values file / GitOps repo) to avoid typos"],"tags":["kubernetes","tailscale-operator","proxygroup","multi-tailnet","configuration"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}