temporalio/temporal · error
invalid schedule: %s
Error message
invalid schedule: %s
What it means
handleListMatchingTimesQuery answers ListScheduleMatchingTimes queries by iterating the scheduler's parsed calendar spec (s.cspec). If cspec is nil — the schedule workflow started before the spec was loaded or the workflow state is corrupt — the query cannot be answered and the workflow's InvalidScheduleError is surfaced.
Source
Thrown at service/worker/scheduler/workflow.go:1170
func (s *scheduler) handleDescribeQuery() (*schedulespb.DescribeResponse, error) {
// this is a query handler, don't modify s.Info directly
infoCopy := common.CloneProto(s.Info)
infoCopy.FutureActionTimes = s.getFutureActionTimes(false, s.tweakables.FutureActionCount)
infoCopy.BufferSize = int64(len(s.State.BufferedStarts))
return &schedulespb.DescribeResponse{
Schedule: s.Schedule,
Info: infoCopy,
ConflictToken: s.State.ConflictToken,
}, nil
}
func (s *scheduler) handleListMatchingTimesQuery(req *workflowservice.ListScheduleMatchingTimesRequest) (*workflowservice.ListScheduleMatchingTimesResponse, error) {
if req == nil || req.StartTime == nil || req.EndTime == nil {
return nil, errors.New("missing or invalid query")
}
if s.cspec == nil {
return nil, fmt.Errorf("invalid schedule: %s", s.Info.InvalidScheduleError)
}
var out []*timestamppb.Timestamp
t1 := timestamp.TimeValue(req.StartTime)
for range maxListMatchingTimesCount {
// don't need to call GetNextTime in SideEffect because this is just a query
res, err := s.cspec.GetNextTime(s.jitterSeed(), t1)
if err != nil {
// An over-excluded spec won't resolve until it's edited, so return a
// non-retryable code: retrying would just re-burn the compute bound each call.
return nil, ErrScheduleSpecLimitHit
}
t1 = res.Next
if t1.IsZero() || t1.After(timestamp.TimeValue(req.EndTime)) {
break
}
out = append(out, timestamppb.New(t1))
}View on GitHub (pinned to bde624efd1)
Solutions
- Retry the query after the schedule workflow has fully started (brief delay)
- Check the schedule with DescribeSchedule and fix the invalid schedule spec reported in InvalidScheduleError
- Delete and recreate the schedule with a valid spec if state is unrecoverable
Example fix
// before
resp, err := frontendClient.ListScheduleMatchingTimes(ctx, req) // immediately after CreateSchedule
// after
schedule.Handle(ctx, client, "sched-id", func(s schedule.ScheduleClientHandle) error {
return require.Eventually-style polling until DescribeSchedule succeeds
}) Defensive patterns
Strategy: try-catch
Validate before calling
// check schedule validity first
_, err := frontendClient.DescribeSchedule(ctx, &workflowservice.DescribeScheduleRequest{ScheduleId: id})
if err != nil { return err } // then inspect Info.InvalidSchedule before querying Try / catch
resp, err := client.ListScheduleMatchingTimes(ctx, req)
var svcErr *serviceerror.InvalidArgument
if errors.As(err, &svcErr) && strings.Contains(svcErr.Message, "invalid schedule") {
// wait for workflow start / inspect InvalidScheduleError via DescribeSchedule
} Prevention
- Don't query ListScheduleMatchingTimes immediately after CreateSchedule; retry briefly
- Inspect DescribeSchedule's invalid-schedule state before issuing matching-times queries
- Recreate schedules from valid specs after failed server migrations
When it happens
Trigger: Sending a ListScheduleMatchingTimes query to a schedule workflow whose cspec is nil, typically right after workflow start before initialization completes, or when the schedule was created invalid/legacy state.
Common situations: Querying a schedule immediately after Describe/Create; schedule workflows recovered from old versions; s.Info.InvalidScheduleError set by an earlier failed spec parse.
Related errors
- missing or invalid query
- out of range
- conflicting timezone names
- ${errs}
- CronString has time zone but missing fields
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/005c0d11f42d4fb4.
Report an issue: GitHub.