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

  1. Confirm the wrapped cause; conflicts resolve on retry, Forbidden needs RBAC repair.
  2. 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.
  3. Pause GitOps sync on that Service while it deletes.
  4. 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

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


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/2705f7ce9b8adfeb. Report an issue: GitHub.