{"record":{"id":"73f1dc71857b2dea","repo":"temporalio/temporal","slug":"historyeventiterator-next-called-without-checkin","errorCode":null,"errorMessage":"HistoryEventIterator Next() called without checking HasNext()","messagePattern":"HistoryEventIterator Next\\(\\) called without checking HasNext\\(\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/collection/paging_iterator.go","lineNumber":71,"sourceCode":"\t}\n\n\t// still have local cached item to return\n\tif iter.nextPageItemIndex < len(iter.pageItems) {\n\t\treturn true\n\t}\n\n\tif len(iter.pageToken) != 0 {\n\t\titer.getNextPage()\n\t\treturn iter.HasNext()\n\t}\n\n\treturn false\n}\n\n// Next return next item or err\nfunc (iter *PagingIteratorImpl[V]) Next() (V, error) {\n\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}","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/collection/paging_iterator.go#L53-L89","documentation":"PagingIteratorImpl.Next() panics if HasNext() is false at call time. The iterator contract requires callers to check HasNext() before every Next(); calling Next() after exhaustion (or before any page is loaded) is a programming error, signaled by this panic (the message text mentions HistoryEventIterator, the original use of this generic iterator).","triggerScenarios":"for { item, err := it.Next(); ... } without an it.HasNext() guard; continuing to call Next() after HasNext() returned false due to a terminal page error or exhausted token.","commonSituations":"Pagination loops over history events / visibility records that assume Next() returns a terminal error instead of requiring HasNext(); refactors that drop the HasNext() check.","solutions":["Use the documented loop shape: for iter.HasNext() { item, err := iter.Next(); ... }","Return on the first non-nil error from Next(); after an error HasNext() becomes false","If integrating third-party loops, wrap Next() with a helper that recovers panics and converts them to errors"],"exampleFix":"// before\nfor {\n  item, err := iter.Next()\n  if err != nil { break }\n  process(item)\n}\n// after\nfor iter.HasNext() {\n  item, err := iter.Next()\n  if err != nil { return err }\n  process(item)\n}","handlingStrategy":"validation","validationCode":"if !iter.HasNext() {\n  return // or break out of the loop; never call Next() here\n}","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(\"Next() misuse: %v\", r) } }()\n  return iter.Next()\n}","preventionTips":["Always iterate with `for iter.HasNext() { item, err := iter.Next(); ... }`","Return immediately on the first Next() error","Add a shared iteration helper so callers never hand-roll the loop"],"tags":["go","panic","iterator","pagination","api-misuse"],"backgroundTag":"iterator-exhausted","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}