sidekiq/sidekiq · error · ArgumentError

array must be an Array

Error message

array must be an Array

What it means

`array_enumerator(array, cursor:)` is the iterable helper that turns a plain Ruby Array into a resumable Enumerator. It only accepts an actual Array; anything else raises ArgumentError immediately, because the cursor logic relies on `each_with_index.drop(cursor)` array semantics.

Source

Thrown at lib/sidekiq/job/iterable/enumerators.rb:21

require_relative "active_record_enumerator"
require_relative "csv_enumerator"

module Sidekiq
  module Job
    module Iterable
      module Enumerators
        # Builds Enumerator object from a given array, using +cursor+ as an offset.
        #
        # @param array [Array]
        # @param cursor [Integer] offset to start iteration from
        #
        # @return [Enumerator]
        #
        # @example
        #   array_enumerator(['build', 'enumerator', 'from', 'any', 'array'], cursor: cursor)
        #
        def array_enumerator(array, cursor:)
          raise ArgumentError, "array must be an Array" unless array.is_a?(Array)

          x = array.each_with_index.drop(cursor || 0)
          x.to_enum { x.size }
        end

        # Builds Enumerator from `ActiveRecord::Relation`.
        # Each Enumerator tick moves the cursor one row forward.
        #
        # @param relation [ActiveRecord::Relation] relation to iterate
        # @param cursor [Object] offset id to start iteration from
        # @param options [Hash] additional options that will be passed to relevant
        #   ActiveRecord batching methods
        #
        # @return [ActiveRecordEnumerator]
        #
        # @example
        #   def build_enumerator(cursor:)
        #     active_record_records_enumerator(User.all, cursor: cursor)

View on GitHub (pinned to 1bb4aa06e5)

Solutions

  1. Materialize to an Array before calling the helper: `array_enumerator(items.to_a, cursor: cursor)`.
  2. For ActiveRecord relations use `active_record_records_enumerator(relation, cursor:)` instead — it batches efficiently and doesn't need an Array.
  3. For other enumerables (Range, Set), call `.to_a` first or build a custom `.to_enum { size }` enumerator.

Example fix

// before
def build_enumerator(cursor:)
  array_enumerator((1..100), cursor: cursor) # Range -> ArgumentError
end

// after
def build_enumerator(cursor:)
  array_enumerator((1..100).to_a, cursor: cursor)
end
Defensive patterns

Strategy: type-guard

Validate before calling

items = items.to_a unless items.is_a?(Array)
raise ArgumentError, 'array_enumerator needs an Array' unless items.is_a?(Array)
array_enumerator(items, cursor: cursor)

Type guard

def array_input?(obj)
  obj.is_a?(Array)
end

Try / catch

begin
  array_enumerator(items, cursor: cursor)
rescue ArgumentError
  array_enumerator(items.to_a, cursor: cursor) # only if items is safely materializable
end

Prevention

When it happens

Trigger: Passing a Range (`1..100`), a Set, an ActiveRecord::Relation that was `.to_a`-ed conditionally, an Enumerator, or nil to `array_enumerator`. The raise happens when `build_enumerator` runs on the worker (or in tests that instantiate the enumerator).

Common situations: Calling `.map`/`.select` chains that lazily return enumerators instead of arrays. Passing `Model.pluck(:id)` is fine (Array), but `Model.all` (Relation) or `1..50.times` style values are not. Data loaded from JSON or params that is actually nil or a Hash.

Related errors


AI-assisted analysis of sidekiq/sidekiq@1bb4aa06e5 (2026-08-21). Data as JSON: /api/errors/09be3ea6f43dbe01. Report an issue: GitHub.