derailed/k9s · error

expecting Statefulset resource

Error message

expecting Statefulset resource

What it means

StatefulSet.GetInstance fetches the object via the factory and converts the unstructured payload into a typed appsv1.StatefulSet with runtime.DefaultUnstructuredConverter.FromUnstructured. If the returned object's shape does not satisfy the StatefulSet schema, the conversion error is replaced by this sentinel message. Typical root causes are an apiVersion mismatch (apps/v1 vs beta) or schema drift between the cached object and the compiled-in type.

Source

Thrown at internal/dao/sts.go:71

	return scaleRes(ctx, s.getFactory(), client.StsGVR, path, replicas)
}

// Restart a StatefulSet rollout.
func (s *StatefulSet) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
	return restartRes[*appsv1.StatefulSet](ctx, s.getFactory(), client.StsGVR, path, opts)
}

// GetInstance returns a statefulset instance.
func (*StatefulSet) GetInstance(f Factory, fqn string) (*appsv1.StatefulSet, error) {
	o, err := f.Get(client.StsGVR, fqn, true, labels.Everything())
	if err != nil {
		return nil, err
	}

	var sts appsv1.StatefulSet
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sts)
	if err != nil {
		return nil, errors.New("expecting Statefulset resource")
	}

	return &sts, nil
}

// TailLogs tail logs for all pods represented by this StatefulSet.
func (s *StatefulSet) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
	sts, err := s.getStatefulSet(opts.Path)
	if err != nil {
		return nil, errors.New("expecting StatefulSet resource")
	}
	if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 {
		return nil, fmt.Errorf("no valid selector found on statefulset: %s", opts.Path)
	}

	return podLogs(ctx, sts.Spec.Selector.MatchLabels, opts)
}

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Check the object's apiVersion/kind first: it must be apps/v1 StatefulSet for the conversion to succeed.
  2. Refresh the cache (restart k9s / re-list) if the cluster was just upgraded or the CRD changed.
  3. Wrap the conversion with the underlying err (fmt.Errorf with %w) to see exactly which field failed instead of the generic message.

Example fix

// before
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sts)
if err != nil { return nil, errors.New("expecting Statefulset resource") }
// after
if err != nil { return nil, fmt.Errorf("expecting Statefulset resource: %w", err) }
Defensive patterns

Strategy: type-guard

Validate before calling

u, ok := o.(*unstructured.Unstructured)
if !ok || u.GetKind() != "StatefulSet" || u.GetAPIVersion() != "apps/v1" {
    return nil, fmt.Errorf("unexpected kind %s/%s", u.GetAPIVersion(), u.GetKind())
}

Type guard

func isStatefulSet(o runtime.Object) bool {
    u, ok := o.(*unstructured.Unstructured)
    return ok && u.GetKind() == "StatefulSet" && u.GetAPIVersion() == "apps/v1"
}

Try / catch

sts, err := stsDAO.GetInstance(f, fqn)
if err != nil {
    if strings.Contains(err.Error(), "expecting Statefulset resource") {
        // check apiVersion/schema drift or stale cache before retrying
    }
    return err
}

Prevention

When it happens

Trigger: GetInstance called on an object served under a different apiVersion/kind than appsv1.StatefulSet (e.g. leftovers from apps/v1beta2 clusters, or a CRD shadowing the sts GVR); conversion of unknown/renamed fields fails.

Common situations: Clusters upgraded from old Kubernetes versions where statefulsets existed in beta apiGroups; aggregated APIs or mutating webhooks that inject fields the typed struct rejects; stale informer cache after a CRD/CR version change.

Related errors


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