temporalio/temporal · error

Unable to merge range %v with incoming range %v

Error message

Unable to merge range %v with incoming range %v

What it means

Range.Merge panics when CanMerge(input) is false, i.e. the two ranges are neither adjacent nor overlapping, so their union would not be a continuous range. Merge is meant for combining neighboring task ranges during queue rebalancing.

Source

Thrown at service/history/queues/range.go:75

	if !r.CanSplit(key) {
		panic(fmt.Sprintf("Unable to split range %v at %v", r, key))
	}

	return NewRange(r.InclusiveMin, key), NewRange(key, r.ExclusiveMax)
}

func (r *Range) CanMerge(
	input Range,
) bool {
	return r.InclusiveMin.CompareTo(input.ExclusiveMax) <= 0 &&
		r.ExclusiveMax.CompareTo(input.InclusiveMin) >= 0
}

func (r *Range) Merge(
	input Range,
) Range {
	if !r.CanMerge(input) {
		panic(fmt.Sprintf("Unable to merge range %v with incoming range %v", r, input))
	}

	return NewRange(
		tasks.MinKey(r.InclusiveMin, input.InclusiveMin),
		tasks.MaxKey(r.ExclusiveMax, input.ExclusiveMax),
	)
}

func (r *Range) Equals(
	input Range,
) bool {
	return r.InclusiveMin.CompareTo(input.InclusiveMin) == 0 &&
		r.ExclusiveMax.CompareTo(input.ExclusiveMax) == 0
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Check r.CanMerge(input) before calling Merge
  2. Sort ranges by InclusiveMin and only merge consecutive neighbors
  3. Verify both ranges come from the same queue/category before merging
  4. If a gap must be covered, construct a new NewRange(min(mins), max(maxes)) explicitly instead of Merge

Example fix

// before
merged := r.Merge(input)
// after
if r.CanMerge(input) {
    merged = r.Merge(input)
} else {
    merged = tasks.NewRange(
        tasks.MinKey(r.InclusiveMin, input.InclusiveMin),
        tasks.MaxKey(r.ExclusiveMax, input.ExclusiveMax))
}
Defensive patterns

Strategy: validation

Validate before calling

if !r.CanMerge(input) {
    // ranges not adjacent: build a covering range explicitly if needed
    return
}
merged := r.Merge(input)

Try / catch

func safeMergeRange(r queues.Range, input queues.Range) (merged queues.Range) {
    defer func() {
        if rec := recover(); rec != nil {
            merged = r
        }
    }()
    if !r.CanMerge(input) {
        return queues.Range{}
    }
    return r.Merge(input)
}

Prevention

When it happens

Trigger: Calling Merge with a range whose InclusiveMin > r.ExclusiveMax or whose ExclusiveMax < r.InclusiveMin, typically when rebalancing logic pairs non-neighboring queue ranges.

Common situations: Rebalancing algorithms that iterate unordered maps of ranges so pairs aren't adjacent; ranges belonging to different queues or task categories; stale ranges after another shard already split/merged them.

Related errors


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