{"record":{"id":"84ff76392ff36c75","repo":"can1357/oh-my-pi","slug":"parallel-expects-an-iterable-of-zero-arg-callabl-84ff76","errorCode":null,"errorMessage":"parallel() expects an iterable of zero-arg callables","messagePattern":"parallel\\(\\) expects an iterable of zero-arg callables","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/rb/prelude.rb","lineNumber":487,"sourceCode":"            emut.synchronize { errors[idx] = e }\n          end\n        end\n      end\n    end\n    begin\n      threads.each(&:join)\n    rescue Exception # rubocop:disable Lint/RescueException\n      threads.each { |t| (t.kill rescue nil) }\n      raise\n    end\n    raise errors[errors.keys.min] unless errors.empty?\n    results\n  end\n\n  def parallel(thunks)\n    list = thunks.to_a\n    list.each do |t|\n      raise TypeError, \"parallel() expects an iterable of zero-arg callables\" unless t.respond_to?(:call)\n    end\n    __omp_pool_map(list) { |t| t.call }\n  end\n\n  def pipeline(items, *stages)\n    current = items.to_a\n    stages.each do |stage|\n      raise TypeError, \"pipeline() stages must be callables\" unless stage.respond_to?(:call)\n      current = __omp_pool_map(current) { |item| stage.call(item) }\n    end\n    current\n  end\n\n  # -------------------------------------------------------------------------\n  # Progress + budget\n  # -------------------------------------------------------------------------\n\n  def log(message)","sourceCodeStart":469,"sourceCodeEnd":505,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/rb/prelude.rb#L469-L505","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wrap each operation in a zero-arg lambda: `parallel([-> { fetch_a }, -> { fetch_b }])`.","Use `.method(:name).to_proc` or `-> { obj.name }` to convert method references into callables.","Filter or map the list so every element responds to `#call` before dispatching."],"exampleFix":"// before\nparallel([tool_fetch('a'), tool_fetch('b')])   # runs eagerly / not callables\n// after\nparallel([-> { tool_fetch('a') }, -> { tool_fetch('b') }])","handlingStrategy":"type-guard","validationCode":"raise TypeError, 'parallel() expects callables' unless thunks.respond_to?(:to_a) && thunks.to_a.all? { |t| t.respond_to?(:call) }","typeGuard":"def callable?(obj)\n  obj.respond_to?(:call)\nend","tryCatchPattern":"begin\n  results = parallel(jobs)\nrescue TypeError => e\n  raise unless e.message.include?('zero-arg callables')\n  results = parallel(jobs.map { |j| -> { j } }) # wrap raw values as thunks\nend","preventionTips":["Always pass lambdas/procs that take zero arguments: `-> { work }`.","Never pass eagerly-computed values or Symbols into parallel().","Add a preflight `.all? { |t| t.respond_to?(:call) }` check in wrappers around parallel."],"tags":["ruby","type-error","concurrency","eval-prelude"],"backgroundTag":"not-callable","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}