{"record":{"id":"41cd2a62b4d800d9","repo":"temporalio/temporal","slug":"cronstring-does-not-have-interval-after-every","errorCode":null,"errorMessage":"CronString does not have interval after @every","messagePattern":"CronString does not have interval after @every","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/worker/scheduler/calendar.go","lineNumber":309,"sourceCode":"\tcase 7:\n\t\tcal.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]\n\tdefault:\n\t\treturn nil, nil, \"\", errors.New(\"CronString does not have 5-7 fields\")\n\t}\n\n\tstructured, err := parseCalendarToStructured(&cal)\n\tif err != nil {\n\t\treturn nil, nil, \"\", err\n\t}\n\n\treturn structured, nil, tzName, nil\n}\n\nfunc parseCronStringInterval(c string) (*schedulepb.IntervalSpec, error) {\n\t// split after @every\n\t_, interval, found := strings.Cut(c, \" \")\n\tif !found {\n\t\treturn nil, errors.New(\"CronString does not have interval after @every\")\n\t}\n\t// allow @every 14h/3h\n\tinterval, phase, _ := strings.Cut(interval, \"/\")\n\tintervalDuration, err := timestamp.ParseDuration(interval)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif phase == \"\" {\n\t\treturn &schedulepb.IntervalSpec{Interval: durationpb.New(intervalDuration)}, nil\n\t}\n\tphaseDuration, err := timestamp.ParseDuration(phase)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &schedulepb.IntervalSpec{Interval: durationpb.New(intervalDuration), Phase: durationpb.New(phaseDuration)}, nil\n}\n\nfunc handlePredefinedCronStrings(c string) string {","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/worker/scheduler/calendar.go#L291-L327","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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').","Validate the cron string before submitting: ensure it starts with '@every ' followed by a non-empty token.","If a cron expression was intended instead of @every, use standard cron syntax (e.g. '0 */2 * * *') rather than the @every prefix.","Trim and check user-supplied schedule strings at the UI/CLI layer to reject empty @every values early."],"exampleFix":"// before\ncronStr := \"@every\"\nspec, err := parseCronString(cronStr) // error: CronString does not have interval after @every\n// after\ncronStr := \"@every 5m\"\nspec, err := parseCronString(cronStr)","handlingStrategy":"validation","validationCode":"func validEveryCron(c string) bool {\n    if !strings.HasPrefix(c, \"@every \") { return false }\n    return len(strings.TrimSpace(strings.TrimPrefix(c, \"@every \"))) > 0\n}\n// call parseCronString only if validEveryCron(cronStr)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always include a duration after @every (e.g. '@every 30m')","Validate cron strings with a parser before persisting schedule configs","Watch for templates/placeholders left unsubstituted in config files","Prefer standard cron syntax if fixed periodicity at second granularity is not needed"],"tags":["go","scheduler","validation","cron"],"backgroundTag":"invalid-cron-expression","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}