{"record":{"id":"781d9f55e8a93258","repo":"istio/istio","slug":"pod-s-s-is-unexpectedly-not-eligible-for-ambient","errorCode":null,"errorMessage":"pod %s/%s is unexpectedly not eligible for ambient enrollment","messagePattern":"pod (.+?)/(.+?) is unexpectedly not eligible for ambient enrollment","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cni/pkg/nodeagent/cni-watcher.go","lineNumber":233,"sourceCode":"\n\t// The plugin already consulted the k8s API - but on this end handler caches may be stale, so retry a few times if we get no pod.\n\t// if err is returned, we couldn't find the pod\n\t// if nil is returned, we found it but ambient is not enabled\n\tfor ambientPod, err = s.handlers.GetPodIfAmbientEnabled(name, namespace); (err != nil) && (retries < maxStaleRetries); retries++ {\n\t\tlog.Warnf(\"got an event for pod %s in namespace %s not found in current pod cache, retry %d of %d\",\n\t\t\tname, namespace, retries, maxStaleRetries)\n\t\tif !sleep.UntilContext(s.ctx, time.Duration(msInterval)*time.Millisecond) {\n\t\t\treturn nil, fmt.Errorf(\"aborted\")\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get pod %s/%s: %v\", namespace, name, err)\n\t}\n\n\t// This shouldn't happen - the CNI plugin should only invoke us when a pod starts up that already meets\n\t// ambient eligibility requirements.\n\tif ambientPod == nil {\n\t\treturn nil, fmt.Errorf(\"pod %s/%s is unexpectedly not eligible for ambient enrollment\", namespace, name)\n\t}\n\treturn ambientPod, nil\n}\n","sourceCodeStart":215,"sourceCodeEnd":237,"githubUrl":"https://github.com/istio/istio/blob/8dc789c5cf17517c64e3c36cb3288230f149dfae/cni/pkg/nodeagent/cni-watcher.go#L215-L237","documentation":"The pod WAS found and lookup succeeded, but GetPodIfAmbientEnabled returned nil because the enablement selector (ambient mesh label check against pod + namespace labels) did not match. The CNI plugin believed the pod was ambient-eligible; the agent disagreed.","triggerScenarios":"Pod or namespace lacks the ambient enrollment label (istio.io/dataplane-mode=ambient) at handler time, or it was removed; label present on pod but namespace label mismatch (or vice versa) depending on selector semantics; stale plugin-side decision.","commonSituations":"Namespace opt-in vs pod opt-out label combinations; the label added by a mutating webhook that had not run when the CNI plugin checked; label removed while the pod was starting; version skew between plugin and agent on what counts as eligible.","solutions":["Verify labels: `kubectl get pod <pod> -o jsonpath='{.metadata.labels}'` and same for the namespace; check istio.io/dataplane-mode","Ensure mutating webhooks that apply the ambient label run BEFORE CNI ADD (correct webhook priority/failurePolicy)","Recreate or annotate the pod so the CNI plugin re-evaluates eligibility","Confirm istio-cni plugin and agent versions match (no eligibility-criteria skew)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// before invoking enrollment, confirm eligibility the same way the agent does\nns, pod := nsCache.Get(nsName), podCache.Get(podName, nsName)\nif pod == nil || ns == nil || !enablementSelector.Matches(pod, ns.Labels) {\n    return errors.New(\"pod not ambient-eligible; check istio.io/dataplane-mode labels\")\n}","typeGuard":"func isAmbientEligible(pod *corev1.Pod, ns *corev1.Namespace, sel EnablementSelector) bool {\n    if pod == nil || ns == nil {\n        return false\n    }\n    return sel.Matches(pod, ns.Labels)\n}","tryCatchPattern":null,"preventionTips":["Keep plugin and agent eligibility logic in one shared function to avoid skew","Order mutating webhooks so ambient labels exist before CNI ADD","Monitor this error to catch label drift in ambient namespaces"],"tags":["kubernetes","labels","ambient-mesh","istio-cni","eligibility"],"backgroundTag":null,"analyzedSha":"8dc789c5cf17517c64e3c36cb3288230f149dfae","analyzedAt":"2026-08-15T15:16:55.434Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}