{"record":{"id":"609521aa9fb0c859","repo":"hashicorp/nomad","slug":"failed-parsing-cron-expression-s-v-609521","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":5976,"sourceCode":"\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 != \"\" {\n\t\t\treturn CronParseNext(fromTime, p.Spec)\n\t\t}\n\n\t\t// multiple specs parsing\n\t\tvar nextTime time.Time","sourceCodeStart":5958,"sourceCodeEnd":5994,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L5958-L5994","documentation":"In CronParseNext, if cronexpr.Parse itself fails to parse the spec, the parse error is wrapped into this message containing the spec and the underlying error. Unlike the panic-recovery path, this fires before any Next() evaluation, directly from cronexpr.Parse.","triggerScenarios":"Calling structs.CronParseNext (directly or via PeriodicConfig.Next) with a spec string that cronexpr.Parse rejects — wrong field count, invalid tokens, malformed steps.","commonSituations":"Runtime dispatch of a periodic job whose spec bypassed validation (e.g. stored pre-validation or constructed programmatically); config loaded from an API with hand-built specs.","solutions":["Correct the spec string reported in the error to a valid 5-field cron expression.","Run cronexpr.Parse on the spec before calling CronParseNext to fail fast.","Ensure the job goes through job validation (which catches bad specs) before being registered.","Trim whitespace/hidden characters from programmatically built spec strings."],"exampleFix":"// before\nt, err := structs.CronParseNext(now, \"* * *\")\n// after\nt, err := structs.CronParseNext(now, \"* * * * *\")","handlingStrategy":"validation","validationCode":"func safeCronParseNext(from time.Time, spec string) (time.Time, error) {\n    if _, err := cronexpr.Parse(spec); err != nil {\n        return time.Time{}, err // fail before CronParseNext\n    }\n    return structs.CronParseNext(from, spec)\n}","typeGuard":null,"tryCatchPattern":"t, err := structs.CronParseNext(fromTime, spec)\nif err != nil {\n    return time.Time{}, fmt.Errorf(\"skipping eval: %w\", err)\n}","preventionTips":["Parse specs once at config load time; reuse validated expressions.","Route all job registration through the validation endpoint.","Sanitize spec strings (trim, strip control chars) from API input."],"tags":["nomad","cron","validation","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"}