temporalio/temporal · error

Unable to split range %v at %v

Error message

Unable to split range %v at %v

What it means

Range.Split panics when CanSplit(key) is false — the split key is not strictly inside the range (it must be > InclusiveMin and < ExclusiveMax). Splitting at or outside the boundaries would produce an empty or invalid sub-range.

Source

Thrown at service/history/queues/range.go:58

func (r *Range) ContainsRange(
	input Range,
) bool {
	return r.InclusiveMin.CompareTo(input.InclusiveMin) <= 0 &&
		r.ExclusiveMax.CompareTo(input.ExclusiveMax) >= 0
}

func (r *Range) CanSplit(
	key tasks.Key,
) bool {
	return r.ContainsKey(key) || r.ExclusiveMax.CompareTo(key) == 0
}

func (r *Range) Split(
	key tasks.Key,
) (left Range, right Range) {
	if !r.CanSplit(key) {
		panic(fmt.Sprintf("Unable to split range %v at %v", r, key))
	}

	return NewRange(r.InclusiveMin, key), NewRange(key, r.ExclusiveMax)
}

func (r *Range) CanMerge(
	input Range,
) bool {
	return r.InclusiveMin.CompareTo(input.ExclusiveMax) <= 0 &&
		r.ExclusiveMax.CompareTo(input.InclusiveMin) >= 0
}

func (r *Range) Merge(
	input Range,
) Range {
	if !r.CanMerge(input) {
		panic(fmt.Sprintf("Unable to merge range %v with incoming range %v", r, input))
	}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Check r.CanSplit(key) before calling Split
  2. Clamp the split key to strictly interior: max(key, InclusiveMin+1) and ensure < ExclusiveMax
  3. Handle degenerate ranges (no interior point) by returning the range unsplit
  4. In random range generation, draw split keys from the open interval (InclusiveMin, ExclusiveMax)

Example fix

// before
left, right := r.Split(key)
// after
if !r.CanSplit(key) {
    return r, Range{} // or pick a new interior key
}
left, right = r.Split(key)
Defensive patterns

Strategy: validation

Validate before calling

if !r.CanSplit(key) {
    return r, queues.Range{} // degenerate: cannot split
}
left, right := r.Split(key)

Try / catch

func safeSplitRange(r queues.Range, key tasks.Key) (l, right queues.Range) {
    defer func() {
        if rec := recover(); rec != nil {
            l, right = r, queues.Range{}
        }
    }()
    if !r.CanSplit(key) {
        return r, queues.Range{}
    }
    return r.Split(key)
}

Prevention

When it happens

Trigger: Calling Split with key <= r.InclusiveMin or key >= r.ExclusiveMax, e.g. NewRandomOrderedRangesInRange computing split points at range edges, or callers reusing a stale key after the range advanced.

Common situations: Random split-point generation hitting boundary values; single-task ranges (min == max-epsilon) where no interior key exists; off-by-one in shard-splitting logic.

Related errors


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