derailed/k9s · error

expecting ServiceAccount resource

Error message

expecting ServiceAccount resource

What it means

While resolving a workload's secret references, hasSecret follows spec.template.spec.serviceAccountName, fetches that ServiceAccount from the factory, and converts it into v1.ServiceAccount (internal/dao/dp.go:304). If the conversion fails, the sentinel 'expecting ServiceAccount resource' is returned with the real error dropped. Callers inside Deployment/DaemonSet/StatefulSet Scan only log this as a warning and skip the workload, so the scan survives, but direct callers receive the error.

Source

Thrown at internal/dao/dp.go:304

		}
	}

	for _, s := range spec.ImagePullSecrets {
		if s.Name == name {
			return true, nil
		}
	}

	if saName := spec.ServiceAccountName; saName != "" {
		o, err := f.Get(client.SaGVR, client.FQN(ns, saName), wait, labels.Everything())
		if err != nil {
			return false, err
		}

		var sa v1.ServiceAccount
		err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sa)
		if err != nil {
			return false, errors.New("expecting ServiceAccount resource")
		}

		for _, ref := range sa.Secrets {
			if ref.Namespace == ns && ref.Name == name {
				return true, nil
			}
		}
	}

	for i := range spec.Volumes {
		if sec := spec.Volumes[i].Secret; sec != nil {
			if sec.SecretName == name {
				return true, nil
			}
		}
	}

	return false, nil

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Inspect the referenced ServiceAccount: kubectl get serviceaccount <name> -n <ns> -o yaml and fix unexpected or mistyped fields.
  2. Upgrade k9s to a build matching the cluster's Kubernetes minor version.
  3. Confirm serviceaccounts.core is served by the core apiserver and not shadowed.
  4. When patching, wrap the converter error with %w so the offending field is reported.
  5. Optionally skip the workload with a warning instead of propagating the error out of hasSecret.

Example fix

// before
var sa v1.ServiceAccount
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sa)
if err != nil {
    return false, errors.New("expecting ServiceAccount resource")
}
// after
u, ok := o.(*unstructured.Unstructured)
if !ok {
    return false, fmt.Errorf("expected unstructured serviceaccount, got %T", o)
}
var sa v1.ServiceAccount
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &sa); err != nil {
    return false, fmt.Errorf("serviceaccount %q does not match v1 schema: %w", client.FQN(ns, saName), err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

o, err := factory.Get(client.SaGVR, client.FQN(ns, saName), wait, labels.Everything())
if err != nil { return false, err }
u, ok := o.(*unstructured.Unstructured)
if !ok || u.GroupVersionKind().Kind != "ServiceAccount" {
    return false, fmt.Errorf("not a serviceaccount: %s", u.GroupVersionKind())
}

Type guard

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

Try / catch

var sa v1.ServiceAccount
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &sa); err != nil {
    return false, fmt.Errorf("serviceaccount %q conversion: %w", saName, err)
}

Prevention

When it happens

Trigger: A secret reference scan (SecGVR branch of Scan) reaching a workload whose serviceAccountName resolves to a ServiceAccount object that does not conform to the compiled v1.ServiceAccount schema — mistyped metadata/secrets fields, an aggregated source shadowing serviceaccounts.core, or k9s/cluster version skew. Also triggered by embedding code calling hasSecret directly.

Common situations: Clusters with custom apiservers or webhooks that mutate ServiceAccounts; version skew between k9s and the apiserver; scans that warn 'Unable to locate secret' in k9s logs while silently skipping workloads whose SA fails conversion.

Related errors


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