{"record":{"id":"4e36dc09dcff288a","repo":"tailscale/tailscale","slug":"failed-to-get-tailscale-com-connector-w","errorCode":null,"errorMessage":"failed to get tailscale.com Connector: %w","messagePattern":"failed to get tailscale\\.com Connector: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/k8s-operator/connector.go","lineNumber":91,"sourceCode":"\tgaugeConnectorSubnetRouterResources = clientmetric.NewGauge(kubetypes.MetricConnectorWithSubnetRouterCount)\n\t// gaugeConnectorExitNodeResources tracks the number of Connectors currently managed by this operator instance that are exit nodes.\n\tgaugeConnectorExitNodeResources = clientmetric.NewGauge(kubetypes.MetricConnectorWithExitNodeCount)\n\t// gaugeConnectorAppConnectorResources tracks the number of Connectors currently managed by this operator instance that are app connectors.\n\tgaugeConnectorAppConnectorResources = clientmetric.NewGauge(kubetypes.MetricConnectorWithAppConnectorCount)\n)\n\nfunc (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) {\n\tlogger := a.logger.With(\"Connector\", req.Name)\n\tlogger.Debugf(\"starting reconcile\")\n\tdefer logger.Debugf(\"reconcile finished\")\n\n\tcn := new(tsapi.Connector)\n\terr = a.Get(ctx, req.NamespacedName, cn)\n\tif apierrors.IsNotFound(err) {\n\t\tlogger.Debugf(\"Connector 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 Connector: %w\", err)\n\t}\n\tif !cn.DeletionTimestamp.IsZero() {\n\t\tlogger.Debugf(\"Connector is being deleted or should not be exposed, cleaning up resources\")\n\t\tix := xslices.Index(cn.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 := a.maybeCleanupConnector(ctx, logger, cn); err != nil {\n\t\t\treturn reconcile.Result{}, err\n\t\t} else if !done {\n\t\t\tlogger.Debugf(\"Connector resource cleanup not yet finished, will retry...\")\n\t\t\treturn reconcile.Result{RequeueAfter: shortRequeue}, nil\n\t\t}\n\n\t\tcn.Finalizers = append(cn.Finalizers[:ix], cn.Finalizers[ix+1:]...)\n\t\tif err := a.Update(ctx, cn); err != nil {","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/k8s-operator/connector.go#L73-L109","documentation":"Thrown by ConnectorReconciler.Reconcile when the controller-runtime Get of the tailscale.com Connector custom resource fails with an error other than NotFound (NotFound is handled as a benign delete). The %w is the raw API error, so it indicates RBAC denial, CRD schema/CRD-missing problems, or API server failure — not deletion.","triggerScenarios":"a.Get(ctx, req.NamespacedName, cn) on types tsapi.Connector returns an error when the operator lacks connectors get permission, the tailscale.com CRD version installed does not match the operator's stored version (no match for kind, schema mismatch), or the API server errors.","commonSituations":"Operator image upgraded to a version requiring newer CRDs without applying the new CRD manifest; operator ClusterRole missing tailscale.com connectors resources; API server/caching informer startup races right after operator boot.","solutions":["Verify the CRD is installed and versions match: kubectl get crd connectors.tailscale.com and compare spec.versions with the operator release's CRD manifest.","Check RBAC: kubectl auth can-i get connectors.tailscale.com --as=system:serviceaccount:<ns>:operator.","Reinstall the operator chart/manifests wholesale so image, RBAC, and CRDs come from one release.","If it happened only at startup, restart the operator — informer sync races resolve on a clean run."],"exampleFix":"# before: old CRD + new operator image\nkubectl apply -f https://tailscale.com/k8s/old-operator-crds.yaml\n\n# after: apply CRDs bundled with the operator release\nkubectl apply -f https://github.com/tailscale/tailscale/raw/<operator-tag>/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml","handlingStrategy":"retry","validationCode":"// Pre-install: CRD present and servable?\n// kubectl get crd connectors.tailscale.com -o jsonpath='{.status.conditions[?(@.type==\"Established\")].status}'\n// expected: True","typeGuard":"func isCRDMismatch(err error) bool {\n\tvar status *apierrors.StatusError\n\tif errors.As(err, &status) {\n\t\treturn status.ErrStatus.Reason == metav1.StatusReasonNotFound ||\n\t\t\tstatus.ErrStatus.Reason == metav1.StatusReasonForbidden ||\n\t\t\tstrings.Contains(err.Error(), \"no matches for kind\")\n\t}\n\treturn false\n}","tryCatchPattern":"err := a.Get(ctx, req.NamespacedName, cn)\nif apierrors.IsNotFound(err) {\n\treturn reconcile.Result{}, nil\n} else if err != nil {\n\tif isCRDMismatch(err) {\n\t\tlogger.Error(\"Connector CRD/RBAC mismatch — reinstall operator CRDs before retrying\")\n\t}\n\treturn reconcile.Result{}, err\n}","preventionTips":["Install operator image and CRDs from the same release tag.","Pin CRD versions in GitOps and review CRD diffs on operator upgrades.","Add a startup probe that lists tailscale.com CRDs to fail fast on skew.","Keep connectors get/list/watch in the operator ClusterRole."],"tags":["kubernetes","crd","rbac","operator","connector"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}