{"record":{"id":"9ade041c018c15ae","repo":"tailscale/tailscale","slug":"failed-to-remove-finalizer-from-tailnet-q-w","errorCode":null,"errorMessage":"failed to remove finalizer from Tailnet %q: %w","messagePattern":"failed to remove finalizer from Tailnet %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"k8s-operator/reconciler/tailnet/tailnet.go","lineNumber":141,"sourceCode":"\tvar tailnet tsapi.Tailnet\n\terr := r.Get(ctx, req.NamespacedName, &tailnet)\n\tswitch {\n\tcase apierrors.IsNotFound(err):\n\t\treturn reconcile.Result{}, nil\n\tcase err != nil:\n\t\treturn reconcile.Result{}, fmt.Errorf(\"failed to get Tailnet %q: %w\", req.NamespacedName, err)\n\t}\n\n\tif !tailnet.DeletionTimestamp.IsZero() {\n\t\treturn r.delete(ctx, &tailnet)\n\t}\n\n\treturn r.createOrUpdate(ctx, &tailnet)\n}\n\nfunc (r *Reconciler) delete(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) {\n\tif err := reconciler.ClearFinalizer(ctx, r.Client, tailnet, reconciler.Finalizer); err != nil {\n\t\treturn reconcile.Result{}, fmt.Errorf(\"failed to remove finalizer from Tailnet %q: %w\", tailnet.Name, err)\n\t}\n\n\tr.tracker.Remove(tailnet.UID)\n\tr.registry.Remove(tailnet.Name)\n\n\treturn reconcile.Result{}, nil\n}\n\n// Constants for condition reasons.\nconst (\n\tReasonInvalidOAuth  = \"InvalidOAuth\"\n\tReasonInvalidSecret = \"InvalidSecret\"\n\tReasonValid         = \"TailnetValid\"\n)\n\nfunc (r *Reconciler) createOrUpdate(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) {\n\tr.tracker.Add(tailnet.UID)\n","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/k8s-operator/reconciler/tailnet/tailnet.go#L123-L159","documentation":"During Tailnet deletion, reconciler.ClearFinalizer patches the object to drop the operator's finalizer so Kubernetes can complete the delete. If that PATCH fails, this wrapped error is returned and the Tailnet object stays in Terminating because the finalizer was never removed.","triggerScenarios":"The PATCH on the Tailnet failing: operator RBAC missing update/patch on tailnets/finalizers; apiserver errors or optimistic conflicts when other controllers patch the same object concurrently; admission webhooks (OPA/Kyverno) rejecting finalizer removal.","commonSituations":"Operator permissions narrowed after install; policy engines blocking finalizer patches; operator restarted mid-delete with a stale cached object.","solutions":["Verify RBAC on the finalizers subresource: kubectl auth can-i update tailnets/finalizers --as=system:serviceaccount:<ns>:<operator-sa>","Check kubectl describe tailnet <name> for events and webhook denials","Wait for the automatic requeue; conflicts usually clear on the next attempt","If the operator was uninstalled and the object is stuck, strip the finalizer manually: kubectl patch tailnet <name> --type=merge -p '{\"metadata\":{\"finalizers\":[]}}'"],"exampleFix":"# before: object stuck in Terminating after operator removal\nkubectl get tailnet <name>   # shows DELETING forever\n# after: manual finalizer removal\nkubectl patch tailnet <name> --type=merge -p '{\"metadata\":{\"finalizers\":[]}}'","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := reconciler.ClearFinalizer(ctx, r.Client, tailnet, reconciler.Finalizer); err != nil {\n    if apierrors.IsConflict(err) {\n        return reconcile.Result{Requeue: true}, nil // next pass uses a fresh object\n    }\n    return reconcile.Result{}, err\n}","preventionTips":["Never remove operator RBAC piecemeal","Document the manual finalizer-strip command for operator uninstall runbooks","Run a single operator leader (leader election enabled) to avoid racing patches"],"tags":["kubernetes","finalizer","deletion","rbac","tailscale-operator"],"backgroundTag":"kubernetes-finalizer-removal-failed","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"}