derailed/k9s · error

expecting Pod resource

Error message

expecting Pod resource

What it means

Pod.Scan converts every controller-less pod in a namespace into v1.Pod to find ConfigMap, Secret and PVC references (internal/dao/pod.go:301); it backs the xray view and UsedBy on configmaps/secrets/PVCs. If any object fails conversion, the sentinel 'expecting Pod resource' aborts the whole scan and the underlying converter error is discarded.

Source

Thrown at internal/dao/pod.go:301

	}

	return refs, nil
}

// Scan scans for cluster resource refs.
func (p *Pod) Scan(_ context.Context, gvr *client.GVR, fqn string, wait bool) (Refs, error) {
	ns, n := client.Namespaced(fqn)
	oo, err := p.getFactory().List(p.gvr, ns, wait, labels.Everything())
	if err != nil {
		return nil, err
	}

	refs := make(Refs, 0, len(oo))
	for _, o := range oo {
		var pod v1.Pod
		err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
		if err != nil {
			return nil, errors.New("expecting Pod resource")
		}
		// Just pick controller less pods...
		if len(pod.OwnerReferences) > 0 {
			continue
		}
		switch gvr {
		case client.CmGVR:
			if !hasConfigMap(&pod.Spec, n) {
				continue
			}
			refs = append(refs, Ref{
				GVR: p.GVR(),
				FQN: client.FQN(pod.Namespace, pod.Name),
			})
		case client.SecGVR:
			found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait)
			if err != nil {
				slog.Warn("Locate secret failed",

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Audit pods in the namespace (kubectl get pods -o yaml) for unexpected or mistyped fields; fix injector templates or manifests.
  2. Upgrade k9s so its vendored k8s.io/* versions match the cluster minor version.
  3. Verify pods.core is served by the core apiserver and not shadowed.
  4. Restart k9s to rebuild informer caches.
  5. Patch the loop to skip unparsable pods with a warning so one object cannot kill the scan.

Example fix

// before
if err != nil {
    return nil, errors.New("expecting Pod resource")
}
// after (scan loops: degrade per object)
if err != nil {
    slog.Warn("skip unparsable pod", "index", i, "err", err)
    continue
}
Defensive patterns

Strategy: try-catch

Validate before calling

for _, o := range oo {
    u, ok := o.(*unstructured.Unstructured)
    if !ok || u.GroupVersionKind().Kind != "Pod" {
        continue
    }
    // safe to convert below
}

Type guard

func isPod(o runtime.Object) bool {
    u, ok := o.(*unstructured.Unstructured)
    return ok && u.GroupVersionKind().Kind == "Pod" && u.GroupVersionKind().Group == ""
}

Try / catch

if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &pod); err != nil {
    slog.Warn("skip unparsable pod", "err", err)
    continue
}

Prevention

When it happens

Trigger: Opening an xray or reference scan (CmGVR, SecGVR, PvcGVR) in a namespace where one cached pod object cannot convert to the compiled v1.Pod struct — k9s/cluster version skew (pod spec fields changing across Kubernetes releases), injector webhooks writing out-of-schema fields, or an aggregated source shadowing pods.core.

Common situations: xray or UsedBy views failing wholesale after cluster upgrades on older k9s builds; service meshes or mutating sidecar injectors adding non-schema fields to pods; environments running mixed Kubernetes versions.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/8428fd2d8fcb6ead. Report an issue: GitHub.