derailed/k9s · error

expecting cronjob resource

Error message

expecting cronjob resource

What it means

k9s's CronJob DAO fetches a single CronJob from the shared informer cache as *unstructured.Unstructured and converts it into the typed batchv1.CronJob struct with runtime.DefaultUnstructuredConverter.FromUnstructured (internal/dao/cronjob.go:137). The sentinel 'expecting cronjob resource' is returned when that conversion fails, and the underlying converter error is discarded, hiding the real cause. It means the cached object stored under the cronjobs.batch GVR does not fit the k8s.io/api schema the binary was compiled against.

Source

Thrown at internal/dao/cronjob.go:137

				FQN: client.FQN(cj.Namespace, cj.Name),
			})
		}
	}

	return refs, nil
}

// GetInstance fetch a matching cronjob.
func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) {
	o, err := c.getFactory().Get(c.gvr, fqn, true, labels.Everything())
	if err != nil {
		return nil, err
	}

	var cj batchv1.CronJob
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)
	if err != nil {
		return nil, errors.New("expecting cronjob resource")
	}

	return &cj, nil
}

// ToggleSuspend toggles suspend/resume on a CronJob.
func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {
	ns, n := client.Namespaced(path)
	auth, err := c.Client().CanI(ns, c.gvr, n, []string{client.GetVerb, client.UpdateVerb})
	if err != nil {
		return err
	}
	if !auth {
		return fmt.Errorf("user is not authorized to (un)suspend cronjobs")
	}

	dial, err := c.Client().Dial()
	if err != nil {

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Inspect the offending object for mistyped or out-of-schema fields: kubectl get cronjob.v1.batch <name> -n <ns> -o yaml, then fix or remove them via kubectl edit/patch.
  2. Check for GVR shadowing: kubectl api-resources --api-group=batch and confirm cronjobs.batch is served by the core apiserver, not an aggregated apiservice.
  3. Upgrade (or rebuild) k9s so its k8s.io/api, apimachinery and client-go versions match the cluster minor version.
  4. Restart k9s to force informer caches to re-list objects after an upgrade or webhook change.
  5. If you embed this DAO, wrap the converter error with fmt.Errorf and %w to surface the offending field instead of the generic sentinel.

Example fix

// before
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)
if err != nil {
    return nil, errors.New("expecting cronjob resource")
}
// after
u, ok := o.(*unstructured.Unstructured)
if !ok {
    return nil, fmt.Errorf("expected unstructured cronjob, got %T", o)
}
var cj batchv1.CronJob
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &cj); err != nil {
    return nil, fmt.Errorf("cronjob %q does not match v1 schema: %w", fqn, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

o, err := factory.Get(client.CronJobGVR, fqn, true, labels.Everything())
if err != nil { return err }
u, ok := o.(*unstructured.Unstructured)
if !ok || u.GroupVersionKind().Kind != "CronJob" {
    return fmt.Errorf("not a cronjob: %s", u.GroupVersionKind())
}

Type guard

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

Try / catch

var cj batchv1.CronJob
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &cj); err != nil {
    return nil, fmt.Errorf("cronjob %q conversion: %w", fqn, err) // keep root cause, name the object
}

Prevention

When it happens

Trigger: Calling dao.CronJob.GetInstance(fqn) — used by the CronJob detail view, ToggleSuspend and job triggering — when the object carries a field whose JSON type cannot be assigned to the compiled batchv1.CronJob struct: fields added or retyped by a newer/older apiserver, an aggregated API or CRD shadowing cronjobs.batch, or a webhook-patched object with out-of-schema values.

Common situations: Running an old k9s build (stale vendored k8s.io/* libraries) against a much newer cluster or vice versa; clusters with custom mutating webhooks that write non-schema-conformant fields; reading an object right after a cluster upgrade while the informer cache still holds the old shape; embedding k9s DAOs in another tool with a custom factory.

Related errors


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