can1357/oh-my-pi · error · TypeError

parallel() expects an iterable of zero-arg callables

Error message

parallel() expects an iterable of zero-arg callables

What it means

`parallel(thunks)` fans out a list of zero-argument callables across the prelude's worker pool and runs them concurrently. Before dispatching it validates every element responds to `#call`; any non-callable element raises TypeError because the pool map would call it and crash inside a worker thread.

Source

Thrown at packages/coding-agent/src/eval/rb/prelude.rb:487

            emut.synchronize { errors[idx] = e }
          end
        end
      end
    end
    begin
      threads.each(&:join)
    rescue Exception # rubocop:disable Lint/RescueException
      threads.each { |t| (t.kill rescue nil) }
      raise
    end
    raise errors[errors.keys.min] unless errors.empty?
    results
  end

  def parallel(thunks)
    list = thunks.to_a
    list.each do |t|
      raise TypeError, "parallel() expects an iterable of zero-arg callables" unless t.respond_to?(:call)
    end
    __omp_pool_map(list) { |t| t.call }
  end

  def pipeline(items, *stages)
    current = items.to_a
    stages.each do |stage|
      raise TypeError, "pipeline() stages must be callables" unless stage.respond_to?(:call)
      current = __omp_pool_map(current) { |item| stage.call(item) }
    end
    current
  end

  # -------------------------------------------------------------------------
  # Progress + budget
  # -------------------------------------------------------------------------

  def log(message)

View on GitHub (pinned to 9690622007)

Solutions

  1. Wrap each operation in a zero-arg lambda: `parallel([-> { fetch_a }, -> { fetch_b }])`.
  2. Use `.method(:name).to_proc` or `-> { obj.name }` to convert method references into callables.
  3. Filter or map the list so every element responds to `#call` before dispatching.

Example fix

// before
parallel([tool_fetch('a'), tool_fetch('b')])   # runs eagerly / not callables
// after
parallel([-> { tool_fetch('a') }, -> { tool_fetch('b') }])
Defensive patterns

Strategy: type-guard

Validate before calling

raise TypeError, 'parallel() expects callables' unless thunks.respond_to?(:to_a) && thunks.to_a.all? { |t| t.respond_to?(:call) }

Type guard

def callable?(obj)
  obj.respond_to?(:call)
end

Try / catch

begin
  results = parallel(jobs)
rescue TypeError => e
  raise unless e.message.include?('zero-arg callables')
  results = parallel(jobs.map { |j| -> { j } }) # wrap raw values as thunks
end

Prevention

When it happens

Trigger: Passing an array of raw values (`parallel([1,2,3])`), an array of Proc-lookalikes that lack `#call` (e.g. plain Objects or Strings), or a mixed array where one entry is nil, or passing lambdas that take required parameters instead of zero-arg thunks.

Common situations: Translating `Promise.all([fetchA(), fetchB()])` literally as values instead of thunks; forgetting to wrap calls in `-> { ... }`; passing method references captured as Symbols instead of lambdas.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/84ff76392ff36c75. Report an issue: GitHub.