{"record":{"id":"9a0526ad18c1224c","repo":"temporalio/temporal","slug":"historyeventiterator-next-should-return-either-a","errorCode":null,"errorMessage":"HistoryEventIterator Next() should return either a history event or a err","messagePattern":"HistoryEventIterator Next\\(\\) should return either a history event or a err","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/collection/paging_iterator.go","lineNumber":88,"sourceCode":"\tif !iter.HasNext() {\n\t\tpanic(\"HistoryEventIterator Next() called without checking HasNext()\")\n\t}\n\n\tif iter.pageErr != nil {\n\t\terr := iter.pageErr\n\t\titer.pageErr = nil\n\t\tvar v V\n\t\treturn v, err\n\t}\n\n\t// we have cached events\n\tif iter.nextPageItemIndex < len(iter.pageItems) {\n\t\tindex := iter.nextPageItemIndex\n\t\titer.nextPageItemIndex++\n\t\treturn iter.pageItems[index], nil\n\t}\n\n\tpanic(\"HistoryEventIterator Next() should return either a history event or a err\")\n}\n\nfunc (iter *PagingIteratorImpl[V]) getNextPage() {\n\titems, token, err := iter.paginationFn(iter.pageToken)\n\tif err == nil {\n\t\titer.pageItems = items\n\t\titer.pageToken = token\n\t\titer.pageErr = nil\n\t} else {\n\t\titer.pageItems = nil\n\t\titer.pageToken = nil\n\t\titer.pageErr = err\n\t}\n\titer.nextPageItemIndex = 0\n}\n","sourceCodeStart":70,"sourceCodeEnd":104,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/collection/paging_iterator.go#L70-L104","documentation":"PagingIteratorImpl.Next() panics if it reaches the end of the current page while HasNext() was true — i.e. internal state (nextPageItemIndex vs pageItems vs pageToken) is inconsistent and Next() can neither return an item nor an error. This is an internal invariant assertion; the message text again references HistoryEventIterator.","triggerScenarios":"Calling Next() when the page is consumed but no further page was fetched or a terminal condition was reached without HasNext() returning false — typically from concurrent use of the iterator or a paginationFn returning an empty page with a valid token and no error.","commonSituations":"Sharing one PagingIteratorImpl across goroutines without synchronization; a custom paginationFn that returns (nil items, non-empty token, nil err) in a way that confuses page state.","solutions":["Do not use a PagingIteratorImpl concurrently; create one iterator per goroutine","Ensure the paginationFn returns either items, an error, or an empty token to signal end of pagination","Upgrade/inspect the iterator version for fixed page-state handling, and recover panics around iteration if the fn is external"],"exampleFix":"// before\nitems, token, err := paginationFn(token)\nreturn items, token, nil // empty items with token, nil err\n// after\nif len(items) == 0 && token != \"\" {\n  return nil, \"\", fmt.Errorf(\"empty page with token\")\n}\nreturn items, token, err","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"func safeNext[V any](iter *collection.PagingIteratorImpl[V]) (v V, err error) {\n  defer func() { if r := recover(); r != nil { err = fmt.Errorf(\"iterator invariant violated: %v\", r) } }()\n  if !iter.HasNext() { err = fmt.Errorf(\"iterator exhausted\") ; return }\n  return iter.Next()\n}","preventionTips":["Never share a PagingIteratorImpl across goroutines","Ensure paginationFn never returns empty items with a non-empty token and nil error","Report this panic upstream — it usually indicates a library bug rather than caller error"],"tags":["go","panic","iterator","pagination","concurrency"],"backgroundTag":"iterator-state-corruption","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}