tailscale/tailscale · error
failed to get tailscale.com Connector: %w
Error message
failed to get tailscale.com Connector: %w
What it means
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.
Source
Thrown at cmd/k8s-operator/connector.go:91
gaugeConnectorSubnetRouterResources = clientmetric.NewGauge(kubetypes.MetricConnectorWithSubnetRouterCount)
// gaugeConnectorExitNodeResources tracks the number of Connectors currently managed by this operator instance that are exit nodes.
gaugeConnectorExitNodeResources = clientmetric.NewGauge(kubetypes.MetricConnectorWithExitNodeCount)
// gaugeConnectorAppConnectorResources tracks the number of Connectors currently managed by this operator instance that are app connectors.
gaugeConnectorAppConnectorResources = clientmetric.NewGauge(kubetypes.MetricConnectorWithAppConnectorCount)
)
func (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) {
logger := a.logger.With("Connector", req.Name)
logger.Debugf("starting reconcile")
defer logger.Debugf("reconcile finished")
cn := new(tsapi.Connector)
err = a.Get(ctx, req.NamespacedName, cn)
if apierrors.IsNotFound(err) {
logger.Debugf("Connector not found, assuming it was deleted")
return reconcile.Result{}, nil
} else if err != nil {
return reconcile.Result{}, fmt.Errorf("failed to get tailscale.com Connector: %w", err)
}
if !cn.DeletionTimestamp.IsZero() {
logger.Debugf("Connector is being deleted or should not be exposed, cleaning up resources")
ix := xslices.Index(cn.Finalizers, FinalizerName)
if ix < 0 {
logger.Debugf("no finalizer, nothing to do")
return reconcile.Result{}, nil
}
if done, err := a.maybeCleanupConnector(ctx, logger, cn); err != nil {
return reconcile.Result{}, err
} else if !done {
logger.Debugf("Connector resource cleanup not yet finished, will retry...")
return reconcile.Result{RequeueAfter: shortRequeue}, nil
}
cn.Finalizers = append(cn.Finalizers[:ix], cn.Finalizers[ix+1:]...)
if err := a.Update(ctx, cn); err != nil {View on GitHub (pinned to cfe32b8be6)
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.
Example fix
# before: old CRD + new operator image kubectl apply -f https://tailscale.com/k8s/old-operator-crds.yaml # after: apply CRDs bundled with the operator release kubectl apply -f https://github.com/tailscale/tailscale/raw/<operator-tag>/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml
Defensive patterns
Strategy: retry
Validate before calling
// Pre-install: CRD present and servable?
// kubectl get crd connectors.tailscale.com -o jsonpath='{.status.conditions[?(@.type=="Established")].status}'
// expected: True Type guard
func isCRDMismatch(err error) bool {
var status *apierrors.StatusError
if errors.As(err, &status) {
return status.ErrStatus.Reason == metav1.StatusReasonNotFound ||
status.ErrStatus.Reason == metav1.StatusReasonForbidden ||
strings.Contains(err.Error(), "no matches for kind")
}
return false
} Try / catch
err := a.Get(ctx, req.NamespacedName, cn)
if apierrors.IsNotFound(err) {
return reconcile.Result{}, nil
} else if err != nil {
if isCRDMismatch(err) {
logger.Error("Connector CRD/RBAC mismatch — reinstall operator CRDs before retrying")
}
return reconcile.Result{}, err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- error verifying ProxyClass for Connector: %w
- storing device ID in Kubernetes Secret: %w
- storing device IPs and FQDN in Kubernetes Secret: %w
- failed to create or update Role %s: %w
- failed to create or update RoleBinding %s: %w
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/4e36dc09dcff288a.
Report an issue: GitHub.