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
- Check iter.CanSplit(key) before calling Split
- Derive the split key from the iterator's own Range() (e.g. midpoint of InclusiveMin/ExclusiveMax)
- Refresh the key after any prior Split/Merge/Next calls that mutated remainingRange
- 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
- Always gate Split behind CanSplit
- Derive split keys from iter.Range(), not from external tasks
- Recompute keys after any operation that advances the iterator
- Unit-test split behavior at range boundaries
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
- Iterator encountered Next call when there is no next item
- Unable to merge iterator range %v with incoming iterator ran
- Unable to split range %v at %v
- Unable to split scope with range %v at %v
- Unable to split queue slice with range %v at %v
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/92eebe46b23655b8.
Report an issue: GitHub.