{"record":{"id":"114141dac0de0af9","repo":"tailscale/tailscale","slug":"failed-to-add-finalizer-to-peerrelay-q-w","errorCode":null,"errorMessage":"failed to add finalizer to PeerRelay %q: %w","messagePattern":"failed to add finalizer to PeerRelay %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"k8s-operator/reconciler/peerrelay/peerrelay.go","lineNumber":214,"sourceCode":"\tif !pr.DeletionTimestamp.IsZero() {\n\t\treturn r.delete(ctx, logger, &pr)\n\t}\n\n\treturn r.createOrUpdate(ctx, logger, &pr)\n}\n\nfunc (r *Reconciler) reportTailnetUnavailable(ctx context.Context, logger *zap.SugaredLogger, pr *tsapi.PeerRelay, tsErr error) (reconcile.Result, error) {\n\toperatorutils.SetPeerRelayCondition(pr, tsapi.PeerRelayReady, metav1.ConditionFalse, ReasonTailnetUnavailable, tsErr.Error(), r.clock, logger)\n\tif err := r.Status().Update(ctx, pr); err != nil {\n\t\treturn reconcile.Result{}, errors.Join(tsErr, fmt.Errorf(\"failed to update PeerRelay status: %w\", err))\n\t}\n\n\treturn reconcile.Result{}, tsErr\n}\n\nfunc (r *Reconciler) createOrUpdate(ctx context.Context, logger *zap.SugaredLogger, pr *tsapi.PeerRelay) (reconcile.Result, error) {\n\tif err := reconciler.EnsureFinalizer(ctx, r.Client, pr, reconciler.Finalizer); err != nil {\n\t\treturn reconcile.Result{}, fmt.Errorf(\"failed to add finalizer to PeerRelay %q: %w\", pr.Name, err)\n\t}\n\n\tr.tracker.Add(pr.UID)\n\n\treplicas := int32(1)\n\tif pr.Spec.Replicas != nil {\n\t\treplicas = *pr.Spec.Replicas\n\t}\n\n\tr.reissuer.EnsureState(pr.Name, int(replicas))\n\n\t// Belt-and-braces: CEL on the CRD enforces this at admission, but we also validate here to guard against older\n\t// clusters without CEL, resources created before the CRD schema landed, or hand-edited status paths. If the user\n\t// hasn't supplied enough EIPs for the requested replica count we refuse to touch existing state and surface the\n\t// condition so they can fix the spec.\n\tif pr.Spec.AWS != nil && int32(len(pr.Spec.AWS.ElasticIPs)) < replicas {\n\t\tmessage := fmt.Sprintf(\"spec.aws.elasticIPs has %d entries but spec.replicas is %d\", len(pr.Spec.AWS.ElasticIPs), replicas)\n\t\toperatorutils.SetPeerRelayCondition(pr, tsapi.PeerRelayReady, metav1.ConditionFalse, ReasonAWSConfigInvalid, message, r.clock, logger)","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/k8s-operator/reconciler/peerrelay/peerrelay.go#L196-L232","documentation":"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.","triggerScenarios":"Thrown at k8s-operator/reconciler/peerrelay/peerrelay.go:214 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Adding the finalizer to the PeerRelay failed; check RBAC update permission on PeerRelays and retry."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}