{"record":{"id":"4eede3bd75defa6d","repo":"derailed/k9s","slug":"expecting-cronjob-resource-4eede3","errorCode":null,"errorMessage":"expecting cronjob resource","messagePattern":"expecting cronjob resource","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/dao/cronjob.go","lineNumber":137,"sourceCode":"\t\t\t\tFQN: client.FQN(cj.Namespace, cj.Name),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn refs, nil\n}\n\n// GetInstance fetch a matching cronjob.\nfunc (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) {\n\to, err := c.getFactory().Get(c.gvr, fqn, true, labels.Everything())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar cj batchv1.CronJob\n\terr = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)\n\tif err != nil {\n\t\treturn nil, errors.New(\"expecting cronjob resource\")\n\t}\n\n\treturn &cj, nil\n}\n\n// ToggleSuspend toggles suspend/resume on a CronJob.\nfunc (c *CronJob) ToggleSuspend(ctx context.Context, path string) error {\n\tns, n := client.Namespaced(path)\n\tauth, err := c.Client().CanI(ns, c.gvr, n, []string{client.GetVerb, client.UpdateVerb})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !auth {\n\t\treturn fmt.Errorf(\"user is not authorized to (un)suspend cronjobs\")\n\t}\n\n\tdial, err := c.Client().Dial()\n\tif err != nil {","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/dao/cronjob.go#L119-L155","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Check for GVR shadowing: kubectl api-resources --api-group=batch and confirm cronjobs.batch is served by the core apiserver, not an aggregated apiservice.","Upgrade (or rebuild) k9s so its k8s.io/api, apimachinery and client-go versions match the cluster minor version.","Restart k9s to force informer caches to re-list objects after an upgrade or webhook change.","If you embed this DAO, wrap the converter error with fmt.Errorf and %w to surface the offending field instead of the generic sentinel."],"exampleFix":"// before\nerr = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj)\nif err != nil {\n    return nil, errors.New(\"expecting cronjob resource\")\n}\n// after\nu, ok := o.(*unstructured.Unstructured)\nif !ok {\n    return nil, fmt.Errorf(\"expected unstructured cronjob, got %T\", o)\n}\nvar cj batchv1.CronJob\nif err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &cj); err != nil {\n    return nil, fmt.Errorf(\"cronjob %q does not match v1 schema: %w\", fqn, err)\n}","handlingStrategy":"try-catch","validationCode":"o, err := factory.Get(client.CronJobGVR, fqn, true, labels.Everything())\nif err != nil { return err }\nu, ok := o.(*unstructured.Unstructured)\nif !ok || u.GroupVersionKind().Kind != \"CronJob\" {\n    return fmt.Errorf(\"not a cronjob: %s\", u.GroupVersionKind())\n}","typeGuard":"func isCronJob(o runtime.Object) bool {\n    u, ok := o.(*unstructured.Unstructured)\n    return ok && u.GroupVersionKind().Kind == \"CronJob\" && u.GroupVersionKind().Group == \"batch\"\n}","tryCatchPattern":"var cj batchv1.CronJob\nif err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &cj); err != nil {\n    return nil, fmt.Errorf(\"cronjob %q conversion: %w\", fqn, err) // keep root cause, name the object\n}","preventionTips":["Keep k9s and its vendored k8s.io/* libraries within one minor version of the cluster.","Apply schema-valid manifests; avoid raw patches that can inject wrong field types.","In custom DAO code, type-assert to *unstructured.Unstructured before conversion and wrap errors with %w.","After cluster upgrades, restart k9s so informer caches re-list every object."],"tags":["kubernetes","go","type-conversion","cronjob","dao"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}