temporalio/temporal · error

phase is not a valid duration: %w

Error message

phase is not a valid duration: %w

What it means

Returned when the `phase` field of an IntervalSpec in a ScheduleSpec fails duration CheckValid(). Phase offsets the interval schedule within each interval and must be a valid, non-negative protobuf duration; Temporal validates it server-side to keep interval arithmetic well-defined.

Source

Thrown at service/frontend/workflow_handler.go:7021

		namespaceName,
		scheduleID,
		"",
		wh.metricsScope(ctx).WithTags(metrics.CommandTypeTag(enumspb.COMMAND_TYPE_UNSPECIFIED.String())),
		wh.throttledLogger,
		operation,
	)
}

func validateScheduleIntervalDurations(spec *schedulepb.ScheduleSpec) error {
	for _, interval := range spec.GetInterval() {
		if d := interval.GetInterval(); d != nil {
			if err := d.CheckValid(); err != nil {
				return fmt.Errorf("interval is not a valid duration: %w", err)
			}
		}
		if d := interval.GetPhase(); d != nil {
			if err := d.CheckValid(); err != nil {
				return fmt.Errorf("phase is not a valid duration: %w", err)
			}
		}
	}
	return nil
}

func validateScheduleTimestamps(spec *schedulepb.ScheduleSpec) error {
	if err := validateTimestamp(spec.GetStartTime(), "start time"); err != nil {
		return err
	}
	return validateTimestamp(spec.GetEndTime(), "end time")
}

func validateTimestamp(value *timestamppb.Timestamp, field string) error {
	if value != nil {
		if err := value.CheckValid(); err != nil {
			return fmt.Errorf("%s is not a valid timestamp: %w", field, err)
		}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Ensure phase is >= 0 and < the interval duration
  2. Build phase with durationpb.New(time.Duration)
  3. Validate spec client-side before submitting the schedule request

Example fix

// before
spec := &schedulepb.ScheduleSpec{Interval: []*schedulepb.IntervalSpec{{Interval: durationpb.New(time.Hour), Phase: &durationpb.Duration{Nanos: -1}}}}
// after
spec := &schedulepb.ScheduleSpec{Interval: []*schedulepb.IntervalSpec{{Interval: durationpb.New(time.Hour), Phase: durationpb.New(30 * time.Minute)}}}
Defensive patterns

Strategy: validation

Validate before calling

func validPhase(p *durationpb.Duration, interval *durationpb.Duration) bool { return p == nil || (p.CheckValid() == nil && p.AsDuration() >= 0 && p.AsDuration() < interval.AsDuration()) }

Type guard

func hasValidPhase(is *schedulepb.IntervalSpec) bool { return is.GetPhase() == nil || is.GetPhase().CheckValid() == nil }

Try / catch

if err := validateScheduleIntervalDurations(spec); err != nil { return err }

Prevention

When it happens

Trigger: CreateSchedule/UpdateSchedule with a ScheduleSpec whose Interval[*].Phase is negative or carries invalid seconds/nanos values, surfacing through validateScheduleIntervalDurations.

Common situations: Hand-constructed durationpb with negative nanos; copying phase from a misparsed config; version mismatches where an older client sends a malformed phase.

Related errors


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