temporalio/temporal · error

%s End is before Start or not in range [%d-%d]

Error message

%s End is before Start or not in range [%d-%d]

What it means

In makeRange, the end bound of a 'start-end' range expression is parsed with parseValue(rangeParts[1], start, maxVal, ...). If the end value is below start or outside the field's allowed bounds, this error is returned. It conflates two causes: end-before-start and plain out-of-range.

Source

Thrown at service/worker/scheduler/calendar.go:464

		start, end := minVal, maxVal
		if part != "*" {
			if strings.Contains(part, "-") {
				// Only a single dash is allowed to denote a range (e.g. "1-5").
				// Inputs with multiple dashes like "1-5-7" should raise the
				// canonical "too many dashes" error expected by tests.
				if strings.Count(part, "-") > 1 { // no negative numbers are expected in spec
					return nil, fmt.Errorf("%s has too many dashes", field)
				}
				rangeParts := strings.SplitN(part, "-", 2)
				if len(rangeParts) != 2 {
					return nil, fmt.Errorf("%s has too many dashes", field)
				}
				if start, err = parseValue(rangeParts[0], minVal, maxVal, parseMode); err != nil {
					return nil, fmt.Errorf("%s Start is not in range [%d-%d]", field, minVal, maxVal)
				}
				if end, err = parseValue(rangeParts[1], start, maxVal, parseMode); err != nil {
					return nil, fmt.Errorf("%s End is before Start or not in range [%d-%d]", field, minVal, maxVal)
				}
			} else {
				if start, err = parseValue(part, minVal, maxVal, parseMode); err != nil {
					return nil, fmt.Errorf("%s is not in range [%d-%d]", field, minVal, maxVal)
				}
				if !hasStep {
					// if / is present, a single value is treated as that value to the
					// end. otherwise a single value is just the single value.
					end = start
				}
			}
		}
		// Special handling for Sunday: Turn "7" into "0", which may require an extra range.
		// Consider some cases:
		// 0-7 or 1-7   can turn into 0-6
		// 3-7          has to turn into 0,3-6
		// 3-7/3        can turn into 3-6/3  (7 doesn't match)
		// 1-7/2        has to turn into 0,1-6/2

View on GitHub (pinned to bde624efd1)

Solutions

  1. Ensure end >= start within the same range part (use multiple ranges or step expressions instead of wrapping)
  2. Verify the end bound is within the field's [min-max] range
  3. Pre-validate schedule strings with the SDK's schedule parser before workflow creation

Example fix

// before
Hour: "22-2" // wrapping not supported
// after
Hour: "22-23" // plus a second spec entry for "0-2"
Defensive patterns

Strategy: validation

Validate before calling

func validCronRangeOrder(part string) error {
    p := strings.SplitN(part, "-", 2)
    if len(p) != 2 { return nil }
    s, err1 := strconv.Atoi(strings.TrimSpace(p[0]))
    e, err2 := strconv.Atoi(strings.TrimSpace(p[1]))
    if err1 == nil && err2 == nil && e < s {
        return fmt.Errorf("range end %d before start %d (wrapping unsupported)", e, s)
    }
    return nil
}

Prevention

When it happens

Trigger: Range expressions like '10-5' (end before start), '0-99' on a minutes field, or an invalid end token in a Scheduler calendar spec field.

Common situations: Typos transposing range bounds; assuming wrapping ranges (like '22-2' hours) are supported when they are not; copying ranges across fields with different maxima.

Related errors


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