temporalio/temporal · error

HistoryEventIterator Next() should return either a history e

Error message

HistoryEventIterator Next() should return either a history event or a err

What it means

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.

Source

Thrown at common/collection/paging_iterator.go:88

	if !iter.HasNext() {
		panic("HistoryEventIterator Next() called without checking HasNext()")
	}

	if iter.pageErr != nil {
		err := iter.pageErr
		iter.pageErr = nil
		var v V
		return v, err
	}

	// we have cached events
	if iter.nextPageItemIndex < len(iter.pageItems) {
		index := iter.nextPageItemIndex
		iter.nextPageItemIndex++
		return iter.pageItems[index], nil
	}

	panic("HistoryEventIterator Next() should return either a history event or a err")
}

func (iter *PagingIteratorImpl[V]) getNextPage() {
	items, token, err := iter.paginationFn(iter.pageToken)
	if err == nil {
		iter.pageItems = items
		iter.pageToken = token
		iter.pageErr = nil
	} else {
		iter.pageItems = nil
		iter.pageToken = nil
		iter.pageErr = err
	}
	iter.nextPageItemIndex = 0
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Do not use a PagingIteratorImpl concurrently; create one iterator per goroutine
  2. Ensure the paginationFn returns either items, an error, or an empty token to signal end of pagination
  3. Upgrade/inspect the iterator version for fixed page-state handling, and recover panics around iteration if the fn is external

Example fix

// before
items, token, err := paginationFn(token)
return items, token, nil // empty items with token, nil err
// after
if len(items) == 0 && token != "" {
  return nil, "", fmt.Errorf("empty page with token")
}
return items, token, err
Defensive patterns

Strategy: try-catch

Try / catch

func safeNext[V any](iter *collection.PagingIteratorImpl[V]) (v V, err error) {
  defer func() { if r := recover(); r != nil { err = fmt.Errorf("iterator invariant violated: %v", r) } }()
  if !iter.HasNext() { err = fmt.Errorf("iterator exhausted") ; return }
  return iter.Next()
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/9a0526ad18c1224c. Report an issue: GitHub.