{"record":{"id":"ef816e1f75468498","repo":"temporalio/temporal","slug":"iterator-encountered-next-call-when-there-is-no-ne","errorCode":null,"errorMessage":"Iterator encountered Next call when there is no next item","messagePattern":"Iterator encountered Next call when there is no next item","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/queues/iterator.go","lineNumber":55,"sourceCode":"\t\tpaginationFnProvider: paginationFnProvider,\n\t\tremainingRange:       r,\n\n\t\t// lazy initialized to prevent task pre-fetching on creating the iterator\n\t\tpagingIterator: nil,\n\t}\n}\n\nfunc (i *IteratorImpl) HasNext() bool {\n\tif i.pagingIterator == nil {\n\t\ti.pagingIterator = collection.NewPagingIterator(i.paginationFnProvider(i.remainingRange))\n\t}\n\n\treturn i.pagingIterator.HasNext()\n}\n\nfunc (i *IteratorImpl) Next() (tasks.Task, error) {\n\tif !i.HasNext() {\n\t\tpanic(\"Iterator encountered Next call when there is no next item\")\n\t}\n\n\ttask, err := i.pagingIterator.Next()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ti.remainingRange.InclusiveMin = task.GetKey().Next()\n\treturn task, nil\n}\n\nfunc (i *IteratorImpl) Range() Range {\n\treturn i.remainingRange\n}\n\nfunc (i *IteratorImpl) CanSplit(key tasks.Key) bool {\n\treturn i.remainingRange.CanSplit(key)\n}","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/queues/iterator.go#L37-L73","documentation":"IteratorImpl.Next panics if called when HasNext() is false, because the underlying paging iterator has no more tasks. It is a contract-enforcement panic: callers must check HasNext before Next, mirroring database/sql Rows and the Go range-over-iterator misuse pattern.","triggerScenarios":"Calling Next() after the iterator is exhausted, or calling Next() on a freshly created iterator whose range is empty (inclusiveMin == exclusiveMax).","commonSituations":"Loop bugs like for { iter.Next() } without HasNext; continuing a loop after a task error return consumed the last item; empty queue ranges fetched during quiet periods.","solutions":["Guard every Next() with if !iter.HasNext() { break } in the loop","Use a standard for iter.HasNext() { ... } loop structure","Check whether an earlier error path consumed the final task and returned early","Refactor to use the iterator in a range-helper function that encapsulates the HasNext check"],"exampleFix":"// before\nfor {\n    task, err := iter.Next()\n    ...\n}\n// after\nfor iter.HasNext() {\n    task, err := iter.Next()\n    if err != nil {\n        return err\n    }\n    ...\n}","handlingStrategy":"validation","validationCode":"if !iter.HasNext() {\n    return nil // iterator exhausted\n}\ntask, err := iter.Next()","typeGuard":null,"tryCatchPattern":"func safeNext(iter queues.Iterator) (t tasks.Task, err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"iterator next on exhausted iterator: %v\", r)\n        }\n    }()\n    if !iter.HasNext() {\n        return nil, nil\n    }\n    return iter.Next()\n}","preventionTips":["Always loop with for iter.HasNext()","Never call Next() after an error return without re-checking HasNext","Handle empty ranges explicitly before iteration","Prefer range-encapsulating helpers over manual Next calls"],"tags":["go","panic","iterator","contract-violation","history-service"],"backgroundTag":"iterator-exhausted-next","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}