{"record":{"id":"65b6ce8ba562b590","repo":"temporalio/temporal","slug":"phase-is-not-a-valid-duration-w","errorCode":null,"errorMessage":"phase is not a valid duration: %w","messagePattern":"phase is not a valid duration: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/frontend/workflow_handler.go","lineNumber":7021,"sourceCode":"\t\tnamespaceName,\n\t\tscheduleID,\n\t\t\"\",\n\t\twh.metricsScope(ctx).WithTags(metrics.CommandTypeTag(enumspb.COMMAND_TYPE_UNSPECIFIED.String())),\n\t\twh.throttledLogger,\n\t\toperation,\n\t)\n}\n\nfunc validateScheduleIntervalDurations(spec *schedulepb.ScheduleSpec) error {\n\tfor _, interval := range spec.GetInterval() {\n\t\tif d := interval.GetInterval(); d != nil {\n\t\t\tif err := d.CheckValid(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"interval is not a valid duration: %w\", err)\n\t\t\t}\n\t\t}\n\t\tif d := interval.GetPhase(); d != nil {\n\t\t\tif err := d.CheckValid(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"phase is not a valid duration: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateScheduleTimestamps(spec *schedulepb.ScheduleSpec) error {\n\tif err := validateTimestamp(spec.GetStartTime(), \"start time\"); err != nil {\n\t\treturn err\n\t}\n\treturn validateTimestamp(spec.GetEndTime(), \"end time\")\n}\n\nfunc validateTimestamp(value *timestamppb.Timestamp, field string) error {\n\tif value != nil {\n\t\tif err := value.CheckValid(); err != nil {\n\t\t\treturn fmt.Errorf(\"%s is not a valid timestamp: %w\", field, err)\n\t\t}","sourceCodeStart":7003,"sourceCodeEnd":7039,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/frontend/workflow_handler.go#L7003-L7039","documentation":"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.","triggerScenarios":"CreateSchedule/UpdateSchedule with a ScheduleSpec whose Interval[*].Phase is negative or carries invalid seconds/nanos values, surfacing through validateScheduleIntervalDurations.","commonSituations":"Hand-constructed durationpb with negative nanos; copying phase from a misparsed config; version mismatches where an older client sends a malformed phase.","solutions":["Ensure phase is >= 0 and < the interval duration","Build phase with durationpb.New(time.Duration)","Validate spec client-side before submitting the schedule request"],"exampleFix":"// before\nspec := &schedulepb.ScheduleSpec{Interval: []*schedulepb.IntervalSpec{{Interval: durationpb.New(time.Hour), Phase: &durationpb.Duration{Nanos: -1}}}}\n// after\nspec := &schedulepb.ScheduleSpec{Interval: []*schedulepb.IntervalSpec{{Interval: durationpb.New(time.Hour), Phase: durationpb.New(30 * time.Minute)}}}","handlingStrategy":"validation","validationCode":"func validPhase(p *durationpb.Duration, interval *durationpb.Duration) bool { return p == nil || (p.CheckValid() == nil && p.AsDuration() >= 0 && p.AsDuration() < interval.AsDuration()) }","typeGuard":"func hasValidPhase(is *schedulepb.IntervalSpec) bool { return is.GetPhase() == nil || is.GetPhase().CheckValid() == nil }","tryCatchPattern":"if err := validateScheduleIntervalDurations(spec); err != nil { return err }","preventionTips":["Keep phase >= 0 and less than the interval","Use durationpb.New rather than struct literals","Validate before submitting schedule updates"],"tags":["duration","protobuf","schedule","validation","go"],"backgroundTag":"invalid-duration","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}