{"record":{"id":"21bb8dbd20ba6c56","repo":"hashicorp/nomad","slug":"only-cron-or-crons-may-be-used","errorCode":null,"errorMessage":"Only cron or crons may be used","messagePattern":"Only cron or crons may be used","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/structs.go","lineNumber":5918,"sourceCode":"}\n\nfunc (p *PeriodicConfig) Copy() *PeriodicConfig {\n\tif p == nil {\n\t\treturn nil\n\t}\n\tnp := new(PeriodicConfig)\n\t*np = *p\n\treturn np\n}\n\nfunc (p *PeriodicConfig) Validate() error {\n\tif !p.Enabled {\n\t\treturn nil\n\t}\n\n\tvar mErr multierror.Error\n\tif p.Spec != \"\" && len(p.Specs) != 0 {\n\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))","sourceCodeStart":5900,"sourceCodeEnd":5936,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L5900-L5936","documentation":"Returned by PeriodicConfig.Validate() (structs.go) when a periodic job defines BOTH the legacy single 'cron' spec (p.Spec) and the newer multi-spec 'crons' list (p.Specs). Exactly one representation of the schedule must be provided; the error is appended to a multierror.","triggerScenarios":"Submitting a periodic job (nomad job run / /v1/jobs) whose Periodic block sets both `cron = \"...\"` and `crons = [...]`, or code populating both PeriodicConfig.Spec and PeriodicConfig.Specs.","commonSituations":"Migrating an old single-cron periodic job to the multi-cron `crons` field while leaving the old `cron` line in the HCL; templating tools that emit both fields; Nomad version drift where `crons` was added (Nomad 1.0+).","solutions":["Remove the legacy `cron` line and keep only `crons`, or vice versa","If you need multiple schedules, use only the `crons` list","Re-render your job template so only one schedule field is emitted","Run `nomad job validate` before `nomad job run` to catch it early"],"exampleFix":"// before\nperiodic {\n  cron = \"*/5 * * * * *\"\n  crons = [\"*/10 * * * * *\", \"0 * * * * *\"]\n}\n// after\nperiodic {\n  crons = [\"*/10 * * * * *\", \"0 * * * * *\"]\n}","handlingStrategy":"validation","validationCode":"if pc.Spec != \"\" && len(pc.Specs) != 0 {\n    return fmt.Errorf(\"periodic block sets both cron and crons; choose one\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When migrating to `crons`, delete the old `cron` line","Use `nomad job validate` before every `nomad job run`","Ensure templates emit exactly one schedule field","Add CI linting for periodic job HCL"],"tags":["nomad","periodic-job","validation"],"backgroundTag":"conflicting-config-fields","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"}