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

  1. 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.
  2. Check RBAC: kubectl auth can-i get connectors.tailscale.com --as=system:serviceaccount:<ns>:operator.
  3. Reinstall the operator chart/manifests wholesale so image, RBAC, and CRDs come from one release.
  4. 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

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


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/4e36dc09dcff288a. Report an issue: GitHub.