{"record":{"id":"9ee0635ede0049b2","repo":"temporalio/temporal","slug":"key-encountered-positive-overflow","errorCode":null,"errorMessage":"Key encountered positive overflow","messagePattern":"Key encountered positive overflow","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/tasks/key.go","lineNumber":84,"sourceCode":"\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (k Key) Prev() Key {\n\tif k.TaskID == 0 {\n\t\tif k.FireTime.UnixNano() == 0 {\n\t\t\tpanic(\"Key encountered negative underflow\")\n\t\t}\n\t\treturn NewKey(k.FireTime.Add(-time.Nanosecond), math.MaxInt64)\n\t}\n\treturn NewKey(k.FireTime, k.TaskID-1)\n}\n\nfunc (k Key) Next() Key {\n\tif k.TaskID == math.MaxInt64 {\n\t\tif k.FireTime.UnixNano() == math.MaxInt64 {\n\t\t\tpanic(\"Key encountered positive overflow\")\n\t\t}\n\t\treturn NewKey(k.FireTime.Add(time.Nanosecond), 0)\n\t}\n\treturn NewKey(k.FireTime, k.TaskID+1)\n}\n\nfunc (k Key) Sub(subtrahend Key) Key {\n\tborrow := int64(0)\n\tdifferenceTaskID := k.TaskID - subtrahend.TaskID\n\tif differenceTaskID < 0 {\n\t\tborrow = 1\n\t\tdifferenceTaskID += MaximumKey.TaskID\n\t}\n\n\tfireTime := k.FireTime.UnixNano() - borrow\n\tsubtrahendFireTime := subtrahend.FireTime.UnixNano()\n\tif fireTime < subtrahendFireTime {\n\t\tpanic(fmt.Sprintf(\"Task key Sub encountered underflow: self: %v, subtrahend: %v\", k, subtrahend))","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/tasks/key.go#L66-L102","documentation":"Key.Next computes the lexicographic successor of a task key. When TaskID is MaxInt64 it rolls forward to the next nanosecond with TaskID 0; if FireTime is also at math.MaxInt64 nanoseconds there is no successor and it panics to prevent int64 positive overflow. Called from paths like GetAndCompleteHistoryTask when advancing task keys.","triggerScenarios":"Calling Key.Next() on the maximal key NewKey(time.Unix(0, math.MaxInt64).UTC(), math.MaxInt64); realistically when a task fetched from persistence carries a corrupt/overflowing FireTime and the completion path advances past it.","commonSituations":"Corrupted persistence rows with sentinel max timestamps; clock/date bugs writing near-max nanosecond timestamps; tests probing boundary keys; retry loops repeatedly calling Next on the same malformed key.","solutions":["Inspect the offending task's FireTime in persistence; fix the corrupt row or delete/complete it manually.","Guard the caller: check the key against MaximumKey before calling Next().","Trace where the max timestamp was written (often a zero-time or clock misconfiguration serialized as max) and fix the writer.","Add validation when loading tasks so keys near MaximumKey are rejected or repaired at read time."],"exampleFix":"// before\nnextKey := task.GetKey().Next()\n\n// after\nvar nextKey tasks.Key\nif task.GetKey() == tasks.MaximumKey {\n    return serviceerror.NewInternal(\"task key at maximum, cannot advance\")\n}\nnextKey = task.GetKey().Next()","handlingStrategy":"validation","validationCode":"if k.CompareTo(tasks.MaximumKey) >= 0 {\n    return errors.New(\"task key at maximum, cannot advance\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Compare against tasks.MaximumKey before calling Next()","Validate FireTime of tasks loaded from persistence (reject near-max timestamps)","Investigate any task row whose FireTime looks like a sentinel/corrupt timestamp"],"tags":["task-key","panic","integer-overflow","history-service"],"backgroundTag":"task-key-overflow","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}