temporalio/temporal · error

CronString does not have interval after @every

Error message

CronString does not have interval after @every

What it means

parseCronStringInterval handles the @every shorthand for cron strings in schedule specs. After matching an @every prefix, it splits the string on the first space to extract the duration; if there is no space, there is no interval value after @every, so it returns this error. It prevents a bare '@every' from silently producing an invalid or zero interval spec.

Source

Thrown at service/worker/scheduler/calendar.go:309

	case 7:
		cal.Second, cal.Minute, cal.Hour, cal.DayOfMonth, cal.Month, cal.DayOfWeek, cal.Year = toks[0], toks[1], toks[2], toks[3], toks[4], toks[5], toks[6]
	default:
		return nil, nil, "", errors.New("CronString does not have 5-7 fields")
	}

	structured, err := parseCalendarToStructured(&cal)
	if err != nil {
		return nil, nil, "", err
	}

	return structured, nil, tzName, nil
}

func parseCronStringInterval(c string) (*schedulepb.IntervalSpec, error) {
	// split after @every
	_, interval, found := strings.Cut(c, " ")
	if !found {
		return nil, errors.New("CronString does not have interval after @every")
	}
	// allow @every 14h/3h
	interval, phase, _ := strings.Cut(interval, "/")
	intervalDuration, err := timestamp.ParseDuration(interval)
	if err != nil {
		return nil, err
	}
	if phase == "" {
		return &schedulepb.IntervalSpec{Interval: durationpb.New(intervalDuration)}, nil
	}
	phaseDuration, err := timestamp.ParseDuration(phase)
	if err != nil {
		return nil, err
	}
	return &schedulepb.IntervalSpec{Interval: durationpb.New(intervalDuration), Phase: durationpb.New(phaseDuration)}, nil
}

func handlePredefinedCronStrings(c string) string {

View on GitHub (pinned to bde624efd1)

Solutions

  1. Append a valid duration after @every, e.g. '@every 1h30m' (durations parsed by timestamp.ParseDuration support h/m/s compounds and allow the phase form '@every 14h/3h').
  2. Validate the cron string before submitting: ensure it starts with '@every ' followed by a non-empty token.
  3. If a cron expression was intended instead of @every, use standard cron syntax (e.g. '0 */2 * * *') rather than the @every prefix.
  4. Trim and check user-supplied schedule strings at the UI/CLI layer to reject empty @every values early.

Example fix

// before
cronStr := "@every"
spec, err := parseCronString(cronStr) // error: CronString does not have interval after @every
// after
cronStr := "@every 5m"
spec, err := parseCronString(cronStr)
Defensive patterns

Strategy: validation

Validate before calling

func validEveryCron(c string) bool {
    if !strings.HasPrefix(c, "@every ") { return false }
    return len(strings.TrimSpace(strings.TrimPrefix(c, "@every "))) > 0
}
// call parseCronString only if validEveryCron(cronStr)

Prevention

When it happens

Trigger: Calling parseCronString (directly or via schedule spec creation) with a cron string like '@every' with nothing after it, or '@every' followed by only whitespace/newline so strings.Cut on " " finds no separator.

Common situations: Hand-edited schedule configs where the duration was accidentally deleted; templates with a placeholder ('@every {{interval}}') left unsubstituted; trailing whitespace trimmed away leaving a bare @every; copy-paste from docs that dropped the duration.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/41cd2a62b4d800d9. Report an issue: GitHub.