CronJobs that have never fired once
What does ZopNight detect here?
A CronJob with no lastScheduleTime has never run since it was created. Unlike a suspended job this one is active and simply never triggered, usually a cron expression that is valid syntax but describes a moment that has not arrived, such as day 31 in a 30-day month.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1734 · RC-1834 · RC-1934 |
| Category | governance |
| Severity | medium |
| Metric | none — pure configuration read |
| Source | internal/rules/k8s/cronjob_never_scheduled.go |
Where it applies
Active, enabled, and still never fired
This is not the suspended case. The CronJob is enabled, the controller is watching it, and
status.lastScheduleTime is empty. Kubernetes considers everything healthy.
The schedule is syntactically valid (an invalid one would be rejected at admission), but it describes a time that has not occurred since the object was created.
The cron expressions that do this
Impossible or near-impossible dates. 0 0 31 2 * is the 31st of February, which never
arrives. 0 0 30 * * skips February entirely and fires eleven times a year.
Timezone assumptions. Kubernetes CronJob schedules evaluate in UTC unless the cluster runs a
version supporting spec.timeZone and it is set. A job written for 2am local time may be firing
at a very different hour, or a maintenance window may have been missed entirely.
Day-of-month and day-of-week together. In cron these are ORed, not ANDed. 0 0 1 * 1 means
“the 1st of the month or any Monday”, which is almost never what the author intended, though
it fires often rather than never.
A far-future first occurrence. An annual job created in March with a January schedule legitimately shows nothing for ten months, and is not a defect.
Distinguishing broken from simply not due yet
Compare the creation timestamp against the schedule’s natural period. A daily job created a week ago with no run is broken. An annual job created last month is fine.
That check is why the rule surfaces this for review rather than asserting a fault: it cannot know your intended cadence.
Finding CronJobs with a null lastScheduleTime
kubectl get cronjob -A -o json | jq -r ' .items[] | select(.status.lastScheduleTime == null and (.spec.suspend != true)) | "\(.metadata.namespace)/\(.metadata.name)\t\(.spec.schedule)\tcreated=\(.metadata.creationTimestamp)"'Read the schedule column against the creation date. That pairing answers it faster than anything else.