hashicorp/nomad · error

failed parsing cron expression %s: %v

Error message

failed parsing cron expression %s: %v

What it means

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.

Source

Thrown at nomad/structs/structs.go:5998

// Next returns the closest time instant matching the spec that is after the
// passed time. If no matching instance exists, the zero value of time.Time is
// returned. The `time.Location` of the returned value matches that of the
// passed time.
func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) {
	switch p.SpecType {
	case PeriodicSpecCron:
		// Single spec parsing
		if p.Spec != "" {
			return CronParseNext(fromTime, p.Spec)
		}

		// multiple specs parsing
		var nextTime time.Time
		for _, spec := range p.Specs {
			t, err := CronParseNext(fromTime, spec)
			if err != nil {
				return time.Time{}, fmt.Errorf("failed parsing cron expression %s: %v", spec, err)
			}
			if nextTime.IsZero() || t.Before(nextTime) {
				nextTime = t
			}
		}
		return nextTime, nil

	case PeriodicSpecTest:
		split := strings.Split(p.Spec, ",")
		if len(split) == 1 && split[0] == "" {
			return time.Time{}, nil
		}

		// Parse the times
		times := make([]time.Time, len(split))
		for i, s := range split {
			unix, err := strconv.Atoi(s)
			if err != nil {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Fix the spec named in the error message to a valid cron expression and update the job.
  2. Re-run `nomad job validate` on the job file to catch all bad specs before registration.
  3. Remove the offending spec from the Specs list if it is unnecessary.
  4. Add a CI/pre-registration check parsing every spec with cronexpr.

Example fix

// before
periodic {
  specs = ["*/5 * * * *", "60 * * * *"] // 60 is invalid minute
}
// after
periodic {
  specs = ["*/5 * * * *", "0 * * * *"]
}
Defensive patterns

Strategy: validation

Validate before calling

func earliestNext(from time.Time, specs []string) (time.Time, error) {
    for _, s := range specs {
        if _, err := cronexpr.Parse(s); err != nil {
            return time.Time{}, fmt.Errorf("spec %q: %w", s, err)
        }
    }
    return job.Periodic.Next(from)
}

Try / catch

next, err := periodic.Next(now)
if err != nil {
    log.Printf("periodic eval failed: %v", err)
    // skip this tick or fix the spec
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/fedc2d387d1d75bb. Report an issue: GitHub.