{"record":{"id":"beda8d88409218da","repo":"hashicorp/nomad","slug":"invalid-cron-spec-q-v","errorCode":null,"errorMessage":"Invalid cron spec %q: %v","messagePattern":"Invalid cron spec %q: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/structs.go","lineNumber":5936,"sourceCode":"\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Only cron or crons may be used\"))\n\t}\n\tif p.Spec == \"\" && len(p.Specs) == 0 {\n\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Must specify a spec\"))\n\t}\n\n\t// Check if we got a valid time zone\n\tif p.TimeZone != \"\" {\n\t\tif _, err := time.LoadLocation(p.TimeZone); err != nil {\n\t\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Invalid time zone %q: %v\", p.TimeZone, err))\n\t\t}\n\t}\n\n\tswitch p.SpecType {\n\tcase PeriodicSpecCron:\n\t\t// Validate the cron spec\n\t\tif p.Spec != \"\" {\n\t\t\tif _, err := cronexpr.Parse(p.Spec); err != nil {\n\t\t\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Invalid cron spec %q: %v\", p.Spec, err))\n\t\t\t}\n\t\t}\n\t\t// Validate the cron specs\n\t\tfor _, spec := range p.Specs {\n\t\t\tif _, err := cronexpr.Parse(spec); err != nil {\n\t\t\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Invalid cron spec %q: %v\", spec, err))\n\t\t\t}\n\t\t}\n\n\tcase PeriodicSpecTest:\n\t\t// No-op\n\tdefault:\n\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Unknown periodic specification type %q\", p.SpecType))\n\t}\n\n\treturn mErr.ErrorOrNil()\n}\n","sourceCodeStart":5918,"sourceCodeEnd":5954,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L5918-L5954","documentation":"PeriodicConfig.Validate parses the job's single cron Spec with the cronexpr library when SpecType is PeriodicSpecCron. If cronexpr.Parse returns an error for the expression, the error is wrapped and appended to the validation multierror. The expression never evaluated, so the job cannot be scheduled periodically.","triggerScenarios":"Submitting a periodic job with `periodic { spec = \"...\", spec_type = \"cron\" }` where the cron expression fails cronexpr.Parse — e.g. \"* * *\" (too few fields), \"*/abc * * * *\", or stray words.","commonSituations":"Copy-pasting cron expressions from systems with different dialects (e.g. 5-field vs Quartz 6-field); typos in step values; using @daily-style descriptors not supported by cronexpr.","solutions":["Fix the cron expression to a valid cronexpr-parseable spec (5 fields: min hour dom mon dow), e.g. \"*/15 * * * *\".","Test the expression locally with cronexpr.Parse or an online cron validator before submitting.","If multiple schedules are needed, move them into the Specs list, each individually valid.","Verify spec_type is actually \"cron\" so the expression is parsed with the right grammar."],"exampleFix":"// before\nperiodic {\n  spec = \"0 9 * * MON-FRI extra\"\n}\n// after\nperiodic {\n  spec = \"0 9 * * MON-FRI\"\n}","handlingStrategy":"validation","validationCode":"import \"github.com/hashicorp/cronexpr\"\n\nfunc validCron(spec string) bool {\n    _, err := cronexpr.Parse(spec)\n    return err == nil\n}\n// call before submitting: if !validCron(job.Periodic.Spec) { ... }","typeGuard":null,"tryCatchPattern":"t, err := structs.CronParseNext(time.Now(), spec)\nif err != nil {\n    log.Printf(\"bad cron spec %q: %v\", spec, err)\n    // fall back or fix input\n}","preventionTips":["Always run `nomad job validate` before `nomad job run`.","Use a 5-field cron expression; avoid Quartz/@descriptor dialects.","Test expressions with cronexpr.Parse locally first.","Keep specs in a linted config source, not ad-hoc edits."],"tags":["nomad","validation","cron","job-spec"],"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"}