{"record":{"id":"fedc2d387d1d75bb","repo":"hashicorp/nomad","slug":"failed-parsing-cron-expression-s-v-fedc2d","errorCode":null,"errorMessage":"failed parsing cron expression %s: %v","messagePattern":"failed parsing cron expression (.+?): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/structs.go","lineNumber":5998,"sourceCode":"\n// Next returns the closest time instant matching the spec that is after the\n// passed time. If no matching instance exists, the zero value of time.Time is\n// returned. The `time.Location` of the returned value matches that of the\n// passed time.\nfunc (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) {\n\tswitch p.SpecType {\n\tcase PeriodicSpecCron:\n\t\t// Single spec parsing\n\t\tif p.Spec != \"\" {\n\t\t\treturn CronParseNext(fromTime, p.Spec)\n\t\t}\n\n\t\t// multiple specs parsing\n\t\tvar nextTime time.Time\n\t\tfor _, spec := range p.Specs {\n\t\t\tt, err := CronParseNext(fromTime, spec)\n\t\t\tif err != nil {\n\t\t\t\treturn time.Time{}, fmt.Errorf(\"failed parsing cron expression %s: %v\", spec, err)\n\t\t\t}\n\t\t\tif nextTime.IsZero() || t.Before(nextTime) {\n\t\t\t\tnextTime = t\n\t\t\t}\n\t\t}\n\t\treturn nextTime, nil\n\n\tcase PeriodicSpecTest:\n\t\tsplit := strings.Split(p.Spec, \",\")\n\t\tif len(split) == 1 && split[0] == \"\" {\n\t\t\treturn time.Time{}, nil\n\t\t}\n\n\t\t// Parse the times\n\t\ttimes := make([]time.Time, len(split))\n\t\tfor i, s := range split {\n\t\t\tunix, err := strconv.Atoi(s)\n\t\t\tif err != nil {","sourceCodeStart":5980,"sourceCodeEnd":6016,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L5980-L6016","documentation":"PeriodicConfig.Next iterates the multiple Specs entries, calling CronParseNext for each to find the earliest next-fire time. If any individual spec fails (parse error or panic from CronParseNext), the whole computation aborts with this error naming the failing spec and the wrapped cause.","triggerScenarios":"Evaluating the next launch time of a periodic job configured with a Specs list where at least one entry is unparseable or panics cronexpr during evaluation.","commonSituations":"A job registered with an invalid entry in specs (perhaps edited server-side or via API without full validation); scheduler tick encountering a legacy job from an older format.","solutions":["Fix the spec named in the error message to a valid cron expression and update the job.","Re-run `nomad job validate` on the job file to catch all bad specs before registration.","Remove the offending spec from the Specs list if it is unnecessary.","Add a CI/pre-registration check parsing every spec with cronexpr."],"exampleFix":"// before\nperiodic {\n  specs = [\"*/5 * * * *\", \"60 * * * *\"] // 60 is invalid minute\n}\n// after\nperiodic {\n  specs = [\"*/5 * * * *\", \"0 * * * *\"]\n}","handlingStrategy":"validation","validationCode":"func earliestNext(from time.Time, specs []string) (time.Time, error) {\n    for _, s := range specs {\n        if _, err := cronexpr.Parse(s); err != nil {\n            return time.Time{}, fmt.Errorf(\"spec %q: %w\", s, err)\n        }\n    }\n    return job.Periodic.Next(from)\n}","typeGuard":null,"tryCatchPattern":"next, err := periodic.Next(now)\nif err != nil {\n    log.Printf(\"periodic eval failed: %v\", err)\n    // skip this tick or fix the spec\n}","preventionTips":["Validate the entire Specs list at registration time.","Fix the exact spec quoted in the error before re-registering.","Avoid editing specs directly via the raw API; use validated job files.","Monitor scheduler logs for repeated eval failures."],"tags":["nomad","cron","scheduler","validation"],"backgroundTag":"invalid-cron-expression","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}