{"record":{"id":"214f9f068877f413","repo":"temporalio/temporal","slug":"unable-to-split-scope-with-range-v-at-v","errorCode":null,"errorMessage":"Unable to split scope with range %v at %v","messagePattern":"Unable to split scope with range (.+?) at (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/queues/scope.go","lineNumber":42,"sourceCode":"\t}\n}\n\nfunc (s *Scope) Contains(task tasks.Task) bool {\n\treturn s.Range.ContainsKey(task.GetKey()) &&\n\t\ts.Predicate.Test(task)\n}\n\nfunc (s *Scope) CanSplitByRange(\n\tkey tasks.Key,\n) bool {\n\treturn s.Range.CanSplit(key)\n}\n\nfunc (s *Scope) SplitByRange(\n\tkey tasks.Key,\n) (left Scope, right Scope) {\n\tif !s.CanSplitByRange(key) {\n\t\tpanic(fmt.Sprintf(\"Unable to split scope with range %v at %v\", s.Range, key))\n\t}\n\n\tleftRange, rightRange := s.Range.Split(key)\n\treturn NewScope(leftRange, s.Predicate), NewScope(rightRange, s.Predicate)\n}\n\nfunc (s *Scope) SplitByPredicate(\n\tpredicate tasks.Predicate,\n) (pass Scope, fail Scope) {\n\tpassScope := NewScope(\n\t\ts.Range,\n\t\ttasks.AndPredicates(s.Predicate, predicate),\n\t)\n\tfailScope := NewScope(\n\t\ts.Range,\n\t\ttasks.AndPredicates(s.Predicate, predicates.Not(predicate)),\n\t)\n\treturn passScope, failScope","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/queues/scope.go#L24-L60","documentation":"Scope.SplitByRange panics when the requested split key is not strictly inside the scope's range (Range.CanSplit(key) is false). Splitting requires the key to fall strictly between InclusiveMin and ExclusiveMax so both halves are non-empty. This guards against producing degenerate empty scopes.","triggerScenarios":"Calling scope.SplitByRange(key) where key <= range.InclusiveMin or key >= range.ExclusiveMax; splitting an empty or single-point range; calling on a scope whose range was already split at that key.","commonSituations":"Queue load-balancing code that picks split points from stale range metadata; splitting an empty scope recovered from persistence; off-by-one between exclusive/inclusive bounds.","solutions":["Call scope.CanSplitByRange(key) first and only split when it returns true","Clamp/choose the split key strictly inside (InclusiveMin, ExclusiveMax), e.g. a midpoint of the range","Handle empty or un-splittable scopes by returning them unchanged instead of splitting"],"exampleFix":"// before\nleft, right := scope.SplitByRange(key) // panics if key outside range\n\n// after\nvar left, right Scope\nif scope.CanSplitByRange(key) {\n  left, right = scope.SplitByRange(key)\n} else {\n  left, right = scope, NewScope(NewRange(scope.Range.ExclusiveMax, scope.Range.ExclusiveMax), scope.Predicate)\n}","handlingStrategy":"validation","validationCode":"if scope.CanSplitByRange(key) {\n  left, right = scope.SplitByRange(key)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always gate SplitByRange behind CanSplitByRange","Compute split keys as midpoints strictly inside the range","Skip splitting for empty or single-key scopes"],"tags":["go","history-service","task-queue","range-split","panic"],"backgroundTag":"invalid-range-split-key","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}