{"record":{"id":"92eebe46b23655b8","repo":"temporalio/temporal","slug":"unable-to-split-iterator-with-range-v-at-v","errorCode":null,"errorMessage":"Unable to split iterator with range %v at %v","messagePattern":"Unable to split iterator with range (.+?) at (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/queues/iterator.go","lineNumber":77,"sourceCode":"\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ti.remainingRange.InclusiveMin = task.GetKey().Next()\n\treturn task, nil\n}\n\nfunc (i *IteratorImpl) Range() Range {\n\treturn i.remainingRange\n}\n\nfunc (i *IteratorImpl) CanSplit(key tasks.Key) bool {\n\treturn i.remainingRange.CanSplit(key)\n}\n\nfunc (i *IteratorImpl) Split(key tasks.Key) (left Iterator, right Iterator) {\n\tif !i.CanSplit(key) {\n\t\tpanic(fmt.Sprintf(\"Unable to split iterator with range %v at %v\", i.remainingRange, key))\n\t}\n\n\tleftRange, rightRange := i.remainingRange.Split(key)\n\tleft = NewIterator(\n\t\ti.paginationFnProvider,\n\t\tleftRange,\n\t)\n\tright = NewIterator(\n\t\ti.paginationFnProvider,\n\t\trightRange,\n\t)\n\treturn left, right\n}\n\nfunc (i *IteratorImpl) CanMerge(iter Iterator) bool {\n\treturn i.remainingRange.CanMerge(iter.Range())\n}\n","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/queues/iterator.go#L59-L95","documentation":"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).","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\nleft, right := iter.Split(key)\n// after\nif !iter.CanSplit(key) {\n    key = midpoint(iter.Range().InclusiveMin, iter.Range().ExclusiveMax)\n}\nif iter.CanSplit(key) {\n    left, right = iter.Split(key)\n}","handlingStrategy":"validation","validationCode":"if !iter.CanSplit(key) {\n    key = midpointKey(iter.Range().InclusiveMin, iter.Range().ExclusiveMax)\n}\nif iter.CanSplit(key) {\n    left, right = iter.Split(key)\n}","typeGuard":null,"tryCatchPattern":"func safeSplit(iter queues.Iterator, key tasks.Key) (l, r queues.Iterator) {\n    defer func() {\n        if rec := recover(); rec != nil {\n            l, r = iter, nil\n        }\n    }()\n    if !iter.CanSplit(key) {\n        return iter, nil\n    }\n    return iter.Split(key)\n}","preventionTips":["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"],"tags":["go","panic","iterator","range-split","history-service"],"backgroundTag":"invalid-split-key","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}