cilium/cilium · error
failed to update Gateway status while handling the reconcile
Error message
failed to update Gateway status while handling the reconcile error: %w: %w
What it means
This error is thrown by gammaReconciler.handleReconcileErrorWithStatus when the operator fails to update the Gateway Service's status subresource while already handling an original reconcile error. It wraps BOTH errors: the original reconcileErr and the status-update err, using Go's multi-%w wrapping so both can be unwrapped with errors.Is/As. The library throws it so the operator log shows the root cause AND the secondary failure that occurred during error handling.
Source
Thrown at operator/pkg/gateway-api/gamma_reconcile.go:395
setMergedLabelsAndAnnotations(cec, desired)
return nil
})
return err
}
func (r *gammaReconciler) updateStatus(ctx context.Context, original *corev1.Service, new *corev1.Service) error {
oldStatus := original.Status.DeepCopy()
newStatus := new.Status.DeepCopy()
if cmp.Equal(oldStatus, newStatus, cmpopts.IgnoreFields(metav1.Condition{}, lastTransitionTime)) {
return nil
}
return r.client.Status().Update(ctx, new)
}
func (r *gammaReconciler) handleReconcileErrorWithStatus(ctx context.Context, reconcileErr error, original *corev1.Service, modified *corev1.Service) (ctrl.Result, error) {
if err := r.updateStatus(ctx, original, modified); err != nil {
return controllerruntime.Fail(fmt.Errorf("failed to update Gateway status while handling the reconcile error: %w: %w", reconcileErr, err))
}
return controllerruntime.Fail(reconcileErr)
}
func (r *gammaReconciler) updateHTTPRouteStatus(ctx context.Context, original *gatewayv1.HTTPRoute, new *gatewayv1.HTTPRoute) error {
oldStatus := original.Status.DeepCopy()
newStatus := new.Status.DeepCopy()
if cmp.Equal(oldStatus, newStatus, cmpopts.IgnoreFields(metav1.Condition{}, lastTransitionTime)) {
return nil
}
r.logger.DebugContext(ctx, "Updating HTTPRoute status", httpRoute, types.NamespacedName{Name: original.Name, Namespace: original.Namespace})
return r.client.Status().Update(ctx, new)
}
func (r *gammaReconciler) handleHTTPRouteReconcileErrorWithStatus(ctx context.Context, reconcileErr error, original *gatewayv1.HTTPRoute, modified *gatewayv1.HTTPRoute) error {
if err := r.updateHTTPRouteStatus(ctx, original, modified); err != nil {View on GitHub (pinned to ac7b90affa)
Solutions
- Check the second wrapped error (%w after the colon) first — it is usually a 409 Conflict; re-trigger reconciliation (retry backoff) so the next attempt reads a fresh resourceVersion and updates status successfully.
- Verify the operator's RBAC allows updating the status subresource: clusterrole must include 'services/status' update (and gateway API resources if applicable).
- Confirm the managed Service was not deleted out-of-band (GitOps flux/argocd pruning, kubectl delete); restore owner references or re-run reconcile.
- Inspect the first wrapped error to fix the original reconcile failure (e.g. missing GatewayClass, invalid config) — fixing only the status error hides the real problem.
Example fix
// before: rbac allowing only core Services - apiGroups: [""] resources: ["services"] verbs: ["get", "list", "watch", "create", "update"] // after: include the status subresource - apiGroups: [""] resources: ["services", "services/status"] verbs: ["get", "list", "watch", "create", "update"]
Defensive patterns
Strategy: retry
Validate before calling
// before reconcile: verify RBAC and Service existence
svc := &corev1.Service{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: gwName, Namespace: gwNs}, svc); err != nil {
return fmt.Errorf("gateway Service %s/%s not retrievable: %w", gwNs, gwName, err)
}
// ensure SubjectAccessReview allows services/status update Type guard
// unwrap and classify the double-wrapped error
type statusUpdateErr struct{ ReconcileErr, UpdateErr error }
func IsStatusUpdateConflict(err error) bool {
return apierrors.IsConflict(err) || apierrors.IsConflict(errors.Unwrap(errors.Unwrap(err)))
} Try / catch
res, err := reconciler.Reconcile(ctx, req)
if err != nil {
if apierrors.IsConflict(err) || strings.Contains(err.Error(), "failed to update Gateway status") {
// transient: rely on controller-runtime backoff
return // requeue happens automatically
}
log.Error(err, "persistent reconcile failure")
} Prevention
- Include services/status in the operator RBAC before deploying.
- Avoid multiple controllers writing the same Service status concurrently.
- Let controller-runtime exponential backoff handle Conflict errors instead of manual immediate retries.
- Monitor 409 Conflict rates on status subresources to detect fighting controllers.
When it happens
Trigger: Reconcile() hits an error reconciling the Gateway's provisioned Service (reconcileErr), then the follow-up updateStatus call — r.client.Status().Update(ctx, modified) on the corev1.Service — also fails (e.g. due to conflict, RBAC denial on Service/status, or the Service being deleted mid-reconcile).
Common situations: Concurrent controllers updating the same Service status causing 409 Conflict (optimistic concurrency failure, stale resourceVersion); Service deleted by another actor while reconciliation fails; operator service account lacking update permission on services/status; API server transient unavailability.
Related errors
- failed to update HTTPRoute status: %w
- failed to update GRPCRoute status: %w
- ⚠️ unable to restart Cilium Operator pods: %w
- failed to get features status from %s: %w
- failed to get Cilium operator pods: %w
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/e99e4fdf8cfa1607.
Report an issue: GitHub.