{"record":{"id":"96d444e5fa567635","repo":"temporalio/temporal","slug":"unable-to-merge-range-v-with-incoming-range-v","errorCode":null,"errorMessage":"Unable to merge range %v with incoming range %v","messagePattern":"Unable to merge range (.+?) with incoming range (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/queues/range.go","lineNumber":75,"sourceCode":"\tif !r.CanSplit(key) {\n\t\tpanic(fmt.Sprintf(\"Unable to split range %v at %v\", r, key))\n\t}\n\n\treturn NewRange(r.InclusiveMin, key), NewRange(key, r.ExclusiveMax)\n}\n\nfunc (r *Range) CanMerge(\n\tinput Range,\n) bool {\n\treturn r.InclusiveMin.CompareTo(input.ExclusiveMax) <= 0 &&\n\t\tr.ExclusiveMax.CompareTo(input.InclusiveMin) >= 0\n}\n\nfunc (r *Range) Merge(\n\tinput Range,\n) Range {\n\tif !r.CanMerge(input) {\n\t\tpanic(fmt.Sprintf(\"Unable to merge range %v with incoming range %v\", r, input))\n\t}\n\n\treturn NewRange(\n\t\ttasks.MinKey(r.InclusiveMin, input.InclusiveMin),\n\t\ttasks.MaxKey(r.ExclusiveMax, input.ExclusiveMax),\n\t)\n}\n\nfunc (r *Range) Equals(\n\tinput Range,\n) bool {\n\treturn r.InclusiveMin.CompareTo(input.InclusiveMin) == 0 &&\n\t\tr.ExclusiveMax.CompareTo(input.ExclusiveMax) == 0\n}\n","sourceCodeStart":57,"sourceCodeEnd":90,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/queues/range.go#L57-L90","documentation":"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.","triggerScenarios":"Calling Merge with a range whose InclusiveMin > r.ExclusiveMax or whose ExclusiveMax < r.InclusiveMin, typically when rebalancing logic pairs non-neighboring queue ranges.","commonSituations":"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.","solutions":["Check r.CanMerge(input) before calling Merge","Sort ranges by InclusiveMin and only merge consecutive neighbors","Verify both ranges come from the same queue/category before merging","If a gap must be covered, construct a new NewRange(min(mins), max(maxes)) explicitly instead of Merge"],"exampleFix":"// before\nmerged := r.Merge(input)\n// after\nif r.CanMerge(input) {\n    merged = r.Merge(input)\n} else {\n    merged = tasks.NewRange(\n        tasks.MinKey(r.InclusiveMin, input.InclusiveMin),\n        tasks.MaxKey(r.ExclusiveMax, input.ExclusiveMax))\n}","handlingStrategy":"validation","validationCode":"if !r.CanMerge(input) {\n    // ranges not adjacent: build a covering range explicitly if needed\n    return\n}\nmerged := r.Merge(input)","typeGuard":null,"tryCatchPattern":"func safeMergeRange(r queues.Range, input queues.Range) (merged queues.Range) {\n    defer func() {\n        if rec := recover(); rec != nil {\n            merged = r\n        }\n    }()\n    if !r.CanMerge(input) {\n        return queues.Range{}\n    }\n    return r.Merge(input)\n}","preventionTips":["Sort ranges and merge only adjacent neighbors","Gate Merge behind CanMerge at every call site","Confirm both ranges belong to the same queue/category","Prefer explicit NewRange(min, max) when intentionally covering a gap"],"tags":["go","panic","range-merge","validation","history-service"],"backgroundTag":"non-adjacent-range-merge","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}