temporalio/temporal · error

Iterator encountered Next call when there is no next item

Error message

Iterator encountered Next call when there is no next item

What it means

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.

Source

Thrown at service/history/queues/iterator.go:55

		paginationFnProvider: paginationFnProvider,
		remainingRange:       r,

		// lazy initialized to prevent task pre-fetching on creating the iterator
		pagingIterator: nil,
	}
}

func (i *IteratorImpl) HasNext() bool {
	if i.pagingIterator == nil {
		i.pagingIterator = collection.NewPagingIterator(i.paginationFnProvider(i.remainingRange))
	}

	return i.pagingIterator.HasNext()
}

func (i *IteratorImpl) Next() (tasks.Task, error) {
	if !i.HasNext() {
		panic("Iterator encountered Next call when there is no next item")
	}

	task, err := i.pagingIterator.Next()
	if err != nil {
		return nil, err
	}

	i.remainingRange.InclusiveMin = task.GetKey().Next()
	return task, nil
}

func (i *IteratorImpl) Range() Range {
	return i.remainingRange
}

func (i *IteratorImpl) CanSplit(key tasks.Key) bool {
	return i.remainingRange.CanSplit(key)
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Guard every Next() with if !iter.HasNext() { break } in the loop
  2. Use a standard for iter.HasNext() { ... } loop structure
  3. Check whether an earlier error path consumed the final task and returned early
  4. Refactor to use the iterator in a range-helper function that encapsulates the HasNext check

Example fix

// before
for {
    task, err := iter.Next()
    ...
}
// after
for iter.HasNext() {
    task, err := iter.Next()
    if err != nil {
        return err
    }
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

if !iter.HasNext() {
    return nil // iterator exhausted
}
task, err := iter.Next()

Try / catch

func safeNext(iter queues.Iterator) (t tasks.Task, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("iterator next on exhausted iterator: %v", r)
        }
    }()
    if !iter.HasNext() {
        return nil, nil
    }
    return iter.Next()
}

Prevention

When it happens

Trigger: Calling Next() after the iterator is exhausted, or calling Next() on a freshly created iterator whose range is empty (inclusiveMin == exclusiveMax).

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

Related errors


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