tailscale/tailscale · error
failed to remove finalizer from Tailnet %q: %w
Error message
failed to remove finalizer from Tailnet %q: %w
What it means
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.
Source
Thrown at k8s-operator/reconciler/tailnet/tailnet.go:141
var tailnet tsapi.Tailnet
err := r.Get(ctx, req.NamespacedName, &tailnet)
switch {
case apierrors.IsNotFound(err):
return reconcile.Result{}, nil
case err != nil:
return reconcile.Result{}, fmt.Errorf("failed to get Tailnet %q: %w", req.NamespacedName, err)
}
if !tailnet.DeletionTimestamp.IsZero() {
return r.delete(ctx, &tailnet)
}
return r.createOrUpdate(ctx, &tailnet)
}
func (r *Reconciler) delete(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) {
if err := reconciler.ClearFinalizer(ctx, r.Client, tailnet, reconciler.Finalizer); err != nil {
return reconcile.Result{}, fmt.Errorf("failed to remove finalizer from Tailnet %q: %w", tailnet.Name, err)
}
r.tracker.Remove(tailnet.UID)
r.registry.Remove(tailnet.Name)
return reconcile.Result{}, nil
}
// Constants for condition reasons.
const (
ReasonInvalidOAuth = "InvalidOAuth"
ReasonInvalidSecret = "InvalidSecret"
ReasonValid = "TailnetValid"
)
func (r *Reconciler) createOrUpdate(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) {
r.tracker.Add(tailnet.UID)
View on GitHub (pinned to 6e0912f979)
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":[]}}'
Example fix
# before: object stuck in Terminating after operator removal
kubectl get tailnet <name> # shows DELETING forever
# after: manual finalizer removal
kubectl patch tailnet <name> --type=merge -p '{"metadata":{"finalizers":[]}}' Defensive patterns
Strategy: retry
Try / catch
if err := reconciler.ClearFinalizer(ctx, r.Client, tailnet, reconciler.Finalizer); err != nil {
if apierrors.IsConflict(err) {
return reconcile.Result{Requeue: true}, nil // next pass uses a fresh object
}
return reconcile.Result{}, err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Operator permissions narrowed after install; policy engines blocking finalizer patches; operator restarted mid-delete with a stale cached object.
Related errors
- error listing DNSConfigs: %w
- error determining DNS name for record: %w
- error getting target IPs: %w
- error retrieving 'dnsrecords' ConfigMap: %w
- error updating DNS config: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/9ade041c018c15ae.
Report an issue: GitHub.