temporalio/temporal · error

Unable to split scope with range %v at %v

Error message

Unable to split scope with range %v at %v

What it means

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.

Source

Thrown at service/history/queues/scope.go:42

	}
}

func (s *Scope) Contains(task tasks.Task) bool {
	return s.Range.ContainsKey(task.GetKey()) &&
		s.Predicate.Test(task)
}

func (s *Scope) CanSplitByRange(
	key tasks.Key,
) bool {
	return s.Range.CanSplit(key)
}

func (s *Scope) SplitByRange(
	key tasks.Key,
) (left Scope, right Scope) {
	if !s.CanSplitByRange(key) {
		panic(fmt.Sprintf("Unable to split scope with range %v at %v", s.Range, key))
	}

	leftRange, rightRange := s.Range.Split(key)
	return NewScope(leftRange, s.Predicate), NewScope(rightRange, s.Predicate)
}

func (s *Scope) SplitByPredicate(
	predicate tasks.Predicate,
) (pass Scope, fail Scope) {
	passScope := NewScope(
		s.Range,
		tasks.AndPredicates(s.Predicate, predicate),
	)
	failScope := NewScope(
		s.Range,
		tasks.AndPredicates(s.Predicate, predicates.Not(predicate)),
	)
	return passScope, failScope

View on GitHub (pinned to bde624efd1)

Solutions

  1. Call scope.CanSplitByRange(key) first and only split when it returns true
  2. Clamp/choose the split key strictly inside (InclusiveMin, ExclusiveMax), e.g. a midpoint of the range
  3. Handle empty or un-splittable scopes by returning them unchanged instead of splitting

Example fix

// before
left, right := scope.SplitByRange(key) // panics if key outside range

// after
var left, right Scope
if scope.CanSplitByRange(key) {
  left, right = scope.SplitByRange(key)
} else {
  left, right = scope, NewScope(NewRange(scope.Range.ExclusiveMax, scope.Range.ExclusiveMax), scope.Predicate)
}
Defensive patterns

Strategy: validation

Validate before calling

if scope.CanSplitByRange(key) {
  left, right = scope.SplitByRange(key)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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