tailscale/tailscale · error
failed to remove finalizer: %w
Error message
failed to remove finalizer: %w
What it means
Final step of maybeCleanup: after child resources are deleted, the operator splices FinalizerName out of svc.Finalizers and esr.Update's the Service. Failure here leaves the Service permanently in Terminating (the finalizer is what held deletion open), though the operator keeps retrying the reconcile. This is the classic stuck-finalizer failure mode.
Source
Thrown at cmd/k8s-operator/egress-services.go:431
// Delete the ClusterIP Service and EndpointSlice for the egress
// service.
types := []client.Object{
&corev1.Service{},
&discoveryv1.EndpointSlice{},
}
crl := egressSvcChildResourceLabels(svc)
for _, typ := range types {
if err := esr.DeleteAllOf(ctx, typ, client.InNamespace(esr.tsNamespace), client.MatchingLabels(crl)); err != nil {
return fmt.Errorf("error deleting %s: %w", typ, err)
}
}
ix := slices.Index(svc.Finalizers, FinalizerName)
if ix != -1 {
logger.Debug("Removing Tailscale finalizer from Service")
svc.Finalizers = append(svc.Finalizers[:ix], svc.Finalizers[ix+1:]...)
if err := esr.Update(ctx, svc); err != nil {
return fmt.Errorf("failed to remove finalizer: %w", err)
}
}
esr.mu.Lock()
esr.svcs.Remove(svc.UID)
gaugeEgressServices.Set(int64(esr.svcs.Len()))
esr.mu.Unlock()
logger.Info("successfully cleaned up resources for egress Service")
return nil
}
func (esr *egressSvcsReconciler) maybeCleanupProxyGroupConfig(ctx context.Context, svc *corev1.Service, lg *zap.SugaredLogger) error {
wantsProxyGroup := svc.Annotations[AnnotationProxyGroup]
cond := tsoperator.GetServiceCondition(svc, tsapi.EgressSvcConfigured)
if cond == nil {
return nil
}
ss := strings.Split(cond.Reason, ":")
if len(ss) < 3 {View on GitHub (pinned to cfe32b8be6)
Solutions
- Confirm the wrapped cause; conflicts resolve on retry, Forbidden needs RBAC repair.
- If the Service is stuck and the operator-managed children (labeled svc/endpointslice in the operator namespace) are already gone, manually clear it: kubectl patch svc <name> -n <ns> -p '{"metadata":{"finalizers":null}}' --type=merge.
- Pause GitOps sync on that Service while it deletes.
- Check kubectl get svc -o jsonpath='{.metadata.finalizers}' to confirm removal.
Defensive patterns
Strategy: retry
Try / catch
if err := esr.Update(ctx, svc); err != nil {
if apierrors.IsConflict(err) || apierrors.IsNotFound(err) {
return nil // requeued or object gone; do not wedge cleanup
}
return fmt.Errorf("failed to remove finalizer: %w", err)
} Prevention
- Suspend GitOps sync on an egress Service before deleting it so nothing re-adds finalizers mid-teardown.
- If operator uninstall is intended and children are gone, clear the finalizer manually: kubectl patch svc <n> -p '{"metadata":{"finalizers":null}}' --type=merge.
- Regularly check for Services stuck in Terminating (kubectl get svc -A | grep Terminating) as a stuck-finalizer canary.
When it happens
Trigger: Service Update fails: optimistic-lock conflict with a concurrent writer, RBAC missing services/update, admission webhook rejecting the finalizer removal, or the Service object already gone server-side.
Common situations: Namespace deletion churning the same object; GitOps force-applying the Service (including its finalizers) during deletion; webhooks that block updates to terminating objects.
Related errors
- error deleting egress service config: %w
- failed to add finalizer: %w
- failed to add finalizer: %w
- too many collisions generating new session; please refresh p
- user is not an admin
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/2705f7ce9b8adfeb.
Report an issue: GitHub.