temporalio/temporal · error
phase cannot be greater than Interval
Error message
phase cannot be greater than Interval
What it means
validateInterval requires Phase to be strictly less than Interval. If phase >= interval, every occurrence would be pushed past the interval boundary, collapsing the phase offset into the next period; the scheduler rejects such specs during canonicalizeSpec because they cannot be represented unambiguously.
Source
Thrown at service/worker/scheduler/spec.go:267
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)
}
if cached, ok := b.locationCache.Get(spec.TimezoneName).(*locationAndError); ok {
return cached.loc, cached.err
}
loc, err := time.LoadLocation(spec.TimezoneName)
b.locationCache.Put(spec.TimezoneName, &locationAndError{
loc: loc,
err: err,
})
return loc, errView on GitHub (pinned to bde624efd1)
Solutions
- Reduce the phase to a value in [0, interval), e.g. Interval=24h, Phase=13h is valid but Interval=1h, Phase=13h is not.
- Model absolute offsets by increasing the interval instead (e.g. make Interval daily and keep Phase as the time-of-day offset).
- Zero the phase if no offset is needed.
- Add a client-side check phase < interval before submitting the update.
Example fix
// before
iv := &schedulepb.IntervalSpec{Interval: durationpb.New(time.Hour), Phase: durationpb.New(13 * time.Hour)}
// after
iv := &schedulepb.IntervalSpec{Interval: durationpb.New(24 * time.Hour), Phase: durationpb.New(13 * time.Hour)} Defensive patterns
Strategy: validation
Validate before calling
ivD, phD := iv.GetInterval().AsDuration(), iv.GetPhase().AsDuration()
if phD >= ivD {
return fmt.Errorf("phase %s must be < interval %s", phD, ivD)
} Prevention
- Keep phase strictly less than interval when editing specs
- Model absolute time-of-day offsets by enlarging the interval (e.g. 24h) rather than inflating phase
- Never copy the interval duration into the phase field
- Re-check phase whenever the interval is shortened
When it happens
Trigger: Submitting an IntervalSpec where Phase duration is equal to or larger than the Interval duration — e.g. Interval=1h with Phase=1h or Phase=90m.
Common situations: Misunderstanding phase semantics and setting it as an absolute time-of-day duration (e.g. 13h) on a small interval; spec-building code copying the same duration into both fields; editing interval down without adjusting phase.
Related errors
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/8b2c5ced41990b7a.
Report an issue: GitHub.