{"record":{"id":"bf10d89e0c3d8473","repo":"hashicorp/nomad","slug":"failed-parsing-cron-expression-q-bf10d8","errorCode":null,"errorMessage":"failed parsing cron expression: %q","messagePattern":"failed parsing cron expression: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/structs.go","lineNumber":5971,"sourceCode":"}\n\nfunc (p *PeriodicConfig) Canonicalize() {\n\t// Load the location\n\tl, err := time.LoadLocation(p.TimeZone)\n\tif err != nil {\n\t\tp.location = time.UTC\n\t}\n\n\tp.location = l\n}\n\n// CronParseNext is a helper that parses the next time for the given expression\n// but captures any panic that may occur in the underlying library.\nfunc CronParseNext(fromTime time.Time, spec string) (t time.Time, err error) {\n\tdefer func() {\n\t\tif recover() != nil {\n\t\t\tt = time.Time{}\n\t\t\terr = fmt.Errorf(\"failed parsing cron expression: %q\", spec)\n\t\t}\n\t}()\n\texp, err := cronexpr.Parse(spec)\n\tif err != nil {\n\t\treturn time.Time{}, fmt.Errorf(\"failed parsing cron expression: %s: %v\", spec, err)\n\t}\n\treturn exp.Next(fromTime), nil\n}\n\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 != \"\" {","sourceCodeStart":5953,"sourceCodeEnd":5989,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L5953-L5989","documentation":"CronParseNext wraps cronexpr evaluation and recovers panics from the underlying library, converting a recovered panic into this error carrying the offending spec in %q. It exists because cronexpr can panic on certain expressions during Next() evaluation even after a successful parse.","triggerScenarios":"Calling structs.CronParseNext(fromTime, spec) where the cronexpr library panics while computing the next fire time (e.g. some pathological expressions or nil-internal states).","commonSituations":"Periodic job dispatch/evaluation at runtime computing the next launch time; expressions that parsed fine at validation but panic during iteration; unusual calendar tokens (dom/dow combos) triggering upstream bugs.","solutions":["Simplify the cron expression (avoid exotic dom/dow combinations) and resubmit the job.","Check the cronexpr library version for known panic bugs and upgrade Nomad/library.","Catch the error and fall back to a simpler, well-tested spec like \"*/5 * * * *\".","If reproducible, validate the spec with a standalone cronexpr.Parse + Next harness before scheduling."],"exampleFix":"// before\nt, err := structs.CronParseNext(now, \"0 0 31 2 *\") // pathological: Feb 31\n// after\nt, err := structs.CronParseNext(now, \"0 0 1 2 *\")","handlingStrategy":"try-catch","validationCode":"// pre-parse to catch errors without triggering the panic path:\nif _, err := cronexpr.Parse(spec); err != nil {\n    return fmt.Errorf(\"spec unusable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"t, err := structs.CronParseNext(now, spec)\nif err != nil && strings.HasPrefix(err.Error(), \"failed parsing cron expression\") {\n    t = now.Add(defaultInterval) // fallback schedule\n}","preventionTips":["Always check the error return of CronParseNext; it may indicate a library panic.","Prefer simple specs over exotic dom/dow combinations.","Upgrade Nomad/cronexpr when panic bugs are fixed upstream.","Test periodic jobs in dev with representative evaluation times."],"tags":["nomad","cron","panic-recovery","scheduler"],"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"}