temporalio/temporal · error
interval is nil
Error message
interval is nil
What it means
validateInterval rejects an IntervalSpec that is nil. A nil interval means the interval portion of the schedule spec was never populated, so the scheduler cannot compute occurrences; canonicalizeSpec calls this during spec compilation and fails fast with this error.
Source
Thrown at service/worker/scheduler/spec.go:257
checkRanges(scs.Hour, "Hour", 0, 23)
checkRanges(scs.DayOfMonth, "DayOfMonth", 1, 31)
checkRanges(scs.Month, "Month", 1, 12)
checkRanges(scs.Year, "Year", minCalendarYear, maxCalendarYear)
checkRanges(scs.DayOfWeek, "DayOfWeek", 0, 6)
if len(scs.Comment) > maxCommentLen {
errs = append(errs, "comment is too long")
}
if len(errs) > 0 {
return errors.New("invalid calendar spec: " + strings.Join(errs, ", "))
}
return nil
}
func validateInterval(i *schedulepb.IntervalSpec) error {
if i == nil {
return errors.New("interval is nil")
}
// TODO: use timestamp.ValidateAndCapProtoDuration after switching to state machine based implementation.
// Not adding it to workflow based implementation to avoid potential non-determinism errors.
iv, phase := timestamp.DurationValue(i.Interval), timestamp.DurationValue(i.Phase)
if iv < time.Second {
return errors.New("interval is too small")
} else if phase < 0 {
return errors.New("phase is negative")
} else if phase >= iv {
return errors.New("phase cannot be greater than Interval")
}
return nil
}
func (b *SpecBuilder) loadTimezone(spec *schedulepb.ScheduleSpec) (*time.Location, error) {
if spec.TimezoneData != nil {
return time.LoadLocationFromTZData(spec.TimezoneName, spec.TimezoneData)
}View on GitHub (pinned to bde624efd1)
Solutions
- Ensure the IntervalSpec is fully constructed (both Interval and Phase durations) before including it in the spec.
- Check for nil before appending to spec.Interval and return a clearer config error to the user.
- If an interval schedule was intended, set e.g. i.Interval = durationpb.New(24*time.Hour).
- If a calendar or cron-based spec was intended, remove the empty interval entry entirely instead of sending a nil.
Example fix
// before
var iv *schedulepb.IntervalSpec // left nil
spec.Interval = []*schedulepb.IntervalSpec{iv}
// after
iv := &schedulepb.IntervalSpec{Interval: durationpb.New(24 * time.Hour)}
spec.Interval = []*schedulepb.IntervalSpec{iv} Defensive patterns
Strategy: validation
Validate before calling
if iv == nil {
return errors.New("interval spec must be set before creating schedule")
}
if iv.Interval == nil || iv.Interval.AsDuration() <= 0 {
return errors.New("interval duration must be positive")
} Type guard
func hasInterval(spec *schedulepb.ScheduleSpec) bool {
return len(spec.GetInterval()) > 0 && spec.GetInterval()[0] != nil && spec.GetInterval()[0].GetInterval() != nil
} Prevention
- Never append unassigned interval pointers to spec.Interval
- Check deserialized specs for nil sub-messages before UpdateSchedule
- Remove empty interval entries if a calendar/cron spec was intended instead
- Initialize IntervalSpec with both Interval and Phase fields explicitly
When it happens
Trigger: Submitting a ScheduleSpec whose Interval list contains a nil *schedulepb.IntervalSpec — typically when building the protobuf message with an unpopulated pointer, or an update request that clears the interval field.
Common situations: Constructing schedulepb.ScheduleSpec in Go where an interval variable was declared but not assigned; JSON/YAML deserialization omitting the interval object; partial updates that nil out Interval accidentally.
Related errors
- out of range
- conflicting timezone names
- ${errs}
- CronString has time zone but missing fields
- CronString does not have 5-7 fields
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/e931704bdddcee30.
Report an issue: GitHub.