temporalio/temporal · error

Unable to split iterator with range %v at %v

Error message

Unable to split iterator with range %v at %v

What it means

IteratorImpl.Split panics when CanSplit(key) is false, i.e. the given task key falls outside the iterator's remaining range so the range cannot be bisected at that point. Splitting is only valid at a key within (InclusiveMin, ExclusiveMax).

Source

Thrown at service/history/queues/iterator.go:77

	if err != nil {
		return nil, err
	}

	i.remainingRange.InclusiveMin = task.GetKey().Next()
	return task, nil
}

func (i *IteratorImpl) Range() Range {
	return i.remainingRange
}

func (i *IteratorImpl) CanSplit(key tasks.Key) bool {
	return i.remainingRange.CanSplit(key)
}

func (i *IteratorImpl) Split(key tasks.Key) (left Iterator, right Iterator) {
	if !i.CanSplit(key) {
		panic(fmt.Sprintf("Unable to split iterator with range %v at %v", i.remainingRange, key))
	}

	leftRange, rightRange := i.remainingRange.Split(key)
	left = NewIterator(
		i.paginationFnProvider,
		leftRange,
	)
	right = NewIterator(
		i.paginationFnProvider,
		rightRange,
	)
	return left, right
}

func (i *IteratorImpl) CanMerge(iter Iterator) bool {
	return i.remainingRange.CanMerge(iter.Range())
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Check iter.CanSplit(key) before calling Split
  2. Derive the split key from the iterator's own Range() (e.g. midpoint of InclusiveMin/ExclusiveMax)
  3. Refresh the key after any prior Split/Merge/Next calls that mutated remainingRange
  4. Add a unit test with boundary keys (min, max) for the splitting logic

Example fix

// before
left, right := iter.Split(key)
// after
if !iter.CanSplit(key) {
    key = midpoint(iter.Range().InclusiveMin, iter.Range().ExclusiveMax)
}
if iter.CanSplit(key) {
    left, right = iter.Split(key)
}
Defensive patterns

Strategy: validation

Validate before calling

if !iter.CanSplit(key) {
    key = midpointKey(iter.Range().InclusiveMin, iter.Range().ExclusiveMax)
}
if iter.CanSplit(key) {
    left, right = iter.Split(key)
}

Try / catch

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

Prevention

When it happens

Trigger: Calling Split with a key <= InclusiveMin or >= ExclusiveMax of the iterator's remaining range, typically from queue load-balancing code that computed a split point from a different iterator or a stale range.

Common situations: Concurrent modification of iterator ranges during queue rebalancing; off-by-one in split-point selection; using a key from a task already consumed (advancing the remaining range).

Related errors


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