kubectl wait --for condition=Established crd/<name> is the usual way to
wait for a new CRD before applying resources of its kind.
It waits while the object has no status.conditions. It does not wait when
status.conditions is present and null: it exits at once with an error.
The kadet-hub/recipes CI hit that on 26 October 2026, with kubectl 1.37.1
against a kind node on Kubernetes 1.37.0:
error: kubectl --kubeconfig ... wait --for condition=Established --timeout 60s crd/rabbitmqclusters.rabbitmq.com exited 1 error: .status.conditions accessor error: <nil> is of the type <nil>, expected []interface{} A rerun of the same job passed. The 60 second timeout never came into play.
Why kubectl gives up
kubectl reads the conditions with unstructured.NestedSlice in
checkCondition (staging/src/k8s.io/kubectl/pkg/cmd/wait/condition.go,
v1.37.2).
NestedSlice has three outcomes. A missing field returns "not found", and
checkCondition keeps waiting. A list is compared with the condition. Any
other value, null included, is an error, and kubectl wait returns it
without retrying:
conditions, found, err := unstructured.NestedSlice(obj.Object, "status", "conditions") if err != nil { return false, err } if !found { return false, nil } The message is the one NestedSlice builds in
apimachinery's helpers.go:
%v accessor error: %v is of the type %T, expected []interface{}.
Reproducing it on kind
The CI failure is a race that we could not trigger on purpose. 200 rounds
of kubectl create of a new CRD followed at once by kubectl wait --for all passed, with kubectl 1.37.2 on a kind 0.33.0
condition=Established
cluster running Kubernetes 1.37.0. We have not found which write leaves a
new CRD's conditions null for a moment.
The code path itself is easy to reach with any object whose
status.conditions is null. A CRD that keeps unknown fields lets you
create one:
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: gadgets.example.com spec: group: example.com names: {kind: Gadget, plural: gadgets} scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true Create the object with kubectl create. kubectl apply drops the null,
because in a merge patch null means "delete this field", and the stored
status ends up as {}.
$ printf 'apiVersion: example.com/v1\nkind: Gadget\nmetadata: {name: with-null}\nstatus: {conditions: null}\n' | kubectl create -f - gadget.example.com/with-null created $ kubectl get gadget with-null -o jsonpath='{.status}' {"conditions":null} $ kubectl wait --for condition=Ready --timeout=10s gadget/with-null error: .status.conditions accessor error: <nil> is of the type <nil>, expected []interface{} That returned after 0 seconds with exit code 1. A second Gadget without a
status waited the full 10 seconds and then reported timed out waiting, which is the behaviour the CI job relied on.
for the condition
The JSONPath form does not help:
$ kubectl wait --for=jsonpath='{.status.conditions[?(@.type=="Ready")].status}'=True --timeout=10s gadget/with-null error: <nil> is not array or slice and cannot be filtered It also exits after 0 seconds.
A wait that holds
Polling with kubectl get and retrying on any answer that is not True
treats null, a missing field and a transient API error alike:
timeout 60 bash -c 'until kubectl get crd/rabbitmqclusters.rabbitmq.com \ -o jsonpath="{.status.conditions[?(@.type==\"Established\")].status}" 2>/dev/null \ | grep -qx True; do sleep 1; done' Against the Gadget above, created with null conditions and patched to
Ready=True three seconds later, the same loop with gadget/with-null and
Ready returned 0 after 3 seconds. timeout turns a CRD that never becomes
Established into exit code 124, so the job still fails on a real problem.
The recipe check of kadet-hub does the same in Python since
kadet-hub/recipes#49: it
reads every new CRD with kubectl get -o json once a second and accepts it
when an Established condition has status True, for up to 60 seconds.
That replaced its three kubectl wait --for condition=Established calls.
Originally published at kadet-hub.org.

