hashicorp/nomad · error

failed parsing cron expression: %q

Error message

failed parsing cron expression: %q

What it means

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.

Source

Thrown at nomad/structs/structs.go:5971

}

func (p *PeriodicConfig) Canonicalize() {
	// Load the location
	l, err := time.LoadLocation(p.TimeZone)
	if err != nil {
		p.location = time.UTC
	}

	p.location = l
}

// CronParseNext is a helper that parses the next time for the given expression
// but captures any panic that may occur in the underlying library.
func CronParseNext(fromTime time.Time, spec string) (t time.Time, err error) {
	defer func() {
		if recover() != nil {
			t = time.Time{}
			err = fmt.Errorf("failed parsing cron expression: %q", spec)
		}
	}()
	exp, err := cronexpr.Parse(spec)
	if err != nil {
		return time.Time{}, fmt.Errorf("failed parsing cron expression: %s: %v", spec, err)
	}
	return exp.Next(fromTime), nil
}

// 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 != "" {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Simplify the cron expression (avoid exotic dom/dow combinations) and resubmit the job.
  2. Check the cronexpr library version for known panic bugs and upgrade Nomad/library.
  3. Catch the error and fall back to a simpler, well-tested spec like "*/5 * * * *".
  4. If reproducible, validate the spec with a standalone cronexpr.Parse + Next harness before scheduling.

Example fix

// before
t, err := structs.CronParseNext(now, "0 0 31 2 *") // pathological: Feb 31
// after
t, err := structs.CronParseNext(now, "0 0 1 2 *")
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-parse to catch errors without triggering the panic path:
if _, err := cronexpr.Parse(spec); err != nil {
    return fmt.Errorf("spec unusable: %w", err)
}

Try / catch

t, err := structs.CronParseNext(now, spec)
if err != nil && strings.HasPrefix(err.Error(), "failed parsing cron expression") {
    t = now.Add(defaultInterval) // fallback schedule
}

Prevention

When it happens

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

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

Related errors


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