{"record":{"id":"60b8426f14c1bc75","repo":"derailed/k9s","slug":"user-is-not-authorized-to-un-suspend-cronjobs","errorCode":null,"errorMessage":"user is not authorized to (un)suspend cronjobs","messagePattern":"user is not authorized to \\(un\\)suspend cronjobs","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/dao/cronjob.go","lineNumber":151,"sourceCode":"\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 {\n\t\treturn err\n\t}\n\tcj, err := dial.BatchV1().CronJobs(ns).Get(ctx, n, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif cj.Spec.Suspend != nil {\n\t\tcurrent := !*cj.Spec.Suspend\n\t\tcj.Spec.Suspend = &current\n\t} else {\n\t\ttrueVal := true\n\t\tcj.Spec.Suspend = &trueVal\n\t}\n\t_, err = dial.BatchV1().CronJobs(ns).Update(ctx, cj, metav1.UpdateOptions{})","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/dao/cronjob.go#L133-L169","documentation":"CronJob.ToggleSuspend (internal/dao/cronjob.go:144-155) flips spec.suspend on a CronJob. It first requires get AND update on cronjobs.batch in the namespace (client.CanI); a denial returns this error before any API dial.","triggerScenarios":"Invoking suspend/unsuspend on a CronJob when the authenticated identity lacks either `get cronjobs` or `update cronjobs` (or patch, which RBAC-wise is covered separately - here update is the required verb) in the CronJob's namespace.","commonSituations":"On-call operators with read-only access trying to pause a noisy CronJob; multi-tenant namespaces where only a deployer role may mutate workloads; kubeconfig pointing at the wrong user after a token rotation.","solutions":["Verify: kubectl auth can-i update cronjobs -n <ns> -as=you","Add a Role with apiGroups: [\"batch\"], resources: [\"cronjobs\"], verbs: [\"get\",\"update\"] and bind it","Or patch through a permitted channel (GitOps PR changing spec.suspend) instead of k9s"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-flight the suspend toggle's requirement: get+update on cronjobs.\nfunc CanToggle(c client.Client, ns string) (bool, error) {\n\treturn c.CanI(ns, client.CronJobGVR, \"\", []string{client.GetVerb, client.UpdateVerb})\n}","typeGuard":null,"tryCatchPattern":"if err := cj.ToggleSuspend(ctx, path); err != nil {\n    if strings.Contains(err.Error(), \"not authorized to (un)suspend\") {\n        // surface an actionable hint instead of a raw failure\n        return fmt.Errorf(\"need get+update on cronjobs.batch in %s (kubectl auth can-i update cronjobs -n %s)\", ns, ns)\n    }\n    return err\n}","preventionTips":["Grant [get, update] on cronjobs.batch to on-call roles that must pause schedules","Prefer GitOps PRs for suspend changes in locked-down clusters"],"tags":["go","kubernetes","rbac","k9s","cronjob","authorization"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}