tailscale/tailscale · error

failed to add finalizer to PeerRelay %q: %w

Error message

failed to add finalizer to PeerRelay %q: %w

What it means

Adding the reconciler's finalizer to the PeerRelay object failed, meaning deletion cleanup (devices, Secrets, Services) can't be guaranteed before removal; the create/update path aborts and reconciliation requeues.

Source

Thrown at k8s-operator/reconciler/peerrelay/peerrelay.go:214

	if !pr.DeletionTimestamp.IsZero() {
		return r.delete(ctx, logger, &pr)
	}

	return r.createOrUpdate(ctx, logger, &pr)
}

func (r *Reconciler) reportTailnetUnavailable(ctx context.Context, logger *zap.SugaredLogger, pr *tsapi.PeerRelay, tsErr error) (reconcile.Result, error) {
	operatorutils.SetPeerRelayCondition(pr, tsapi.PeerRelayReady, metav1.ConditionFalse, ReasonTailnetUnavailable, tsErr.Error(), r.clock, logger)
	if err := r.Status().Update(ctx, pr); err != nil {
		return reconcile.Result{}, errors.Join(tsErr, fmt.Errorf("failed to update PeerRelay status: %w", err))
	}

	return reconcile.Result{}, tsErr
}

func (r *Reconciler) createOrUpdate(ctx context.Context, logger *zap.SugaredLogger, pr *tsapi.PeerRelay) (reconcile.Result, error) {
	if err := reconciler.EnsureFinalizer(ctx, r.Client, pr, reconciler.Finalizer); err != nil {
		return reconcile.Result{}, fmt.Errorf("failed to add finalizer to PeerRelay %q: %w", pr.Name, err)
	}

	r.tracker.Add(pr.UID)

	replicas := int32(1)
	if pr.Spec.Replicas != nil {
		replicas = *pr.Spec.Replicas
	}

	r.reissuer.EnsureState(pr.Name, int(replicas))

	// Belt-and-braces: CEL on the CRD enforces this at admission, but we also validate here to guard against older
	// clusters without CEL, resources created before the CRD schema landed, or hand-edited status paths. If the user
	// hasn't supplied enough EIPs for the requested replica count we refuse to touch existing state and surface the
	// condition so they can fix the spec.
	if pr.Spec.AWS != nil && int32(len(pr.Spec.AWS.ElasticIPs)) < replicas {
		message := fmt.Sprintf("spec.aws.elasticIPs has %d entries but spec.replicas is %d", len(pr.Spec.AWS.ElasticIPs), replicas)
		operatorutils.SetPeerRelayCondition(pr, tsapi.PeerRelayReady, metav1.ConditionFalse, ReasonAWSConfigInvalid, message, r.clock, logger)

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Adding the finalizer to the PeerRelay failed; check RBAC update permission on PeerRelays and retry.
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at k8s-operator/reconciler/peerrelay/peerrelay.go:214 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/114141dac0de0af9. Report an issue: GitHub.