{"record":{"id":"c3f793b5356207db","repo":"can1357/oh-my-pi","slug":"pipeline-stages-must-be-callables-c3f793","errorCode":null,"errorMessage":"pipeline() stages must be callables","messagePattern":"pipeline\\(\\) stages must be callables","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/rb/prelude.rb","lineNumber":495,"sourceCode":"      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)\n    __omp_emit_status(\"log\", \"message\" => message.to_s)\n    nil\n  end\n\n  def phase(title)\n    $__omp_current_phase = title.to_s\n    __omp_emit_status(\"phase\", \"title\" => title.to_s)\n    nil","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/rb/prelude.rb#L477-L513","documentation":"`pipeline(items, *stages)` runs each stage concurrently over the item list, feeding each stage's output into the next. Each stage must be a callable (Proc/lambda responding to `#call`); a non-callable stage raises TypeError before any work is dispatched.","triggerScenarios":"Calling `pipeline(items, tool_transform)` where `tool_transform` is a Symbol, String method name, or class rather than a Proc — e.g. `pipeline(rows, :to_json)` or `pipeline(rows, \"normalize\")`.","commonSituations":"Passing method names as Symbols (Rails habit) instead of procs; passing a Hash of stage config instead of a lambda; forgetting `&:symbol` won't work here because the value itself must respond to `#call`.","solutions":["Convert named methods to procs: `pipeline(rows, method(:normalize))`.","Use a lambda per stage: `pipeline(rows, ->(row) { transform(row) })`.","For Symbol shorthand behavior, wrap explicitly: `pipeline(rows, ->(x) { x.to_json })` instead of `:to_json`."],"exampleFix":"// before\npipeline(rows, :normalize)\n// after\npipeline(rows, method(:normalize))   # or ->(row) { normalize(row) }","handlingStrategy":"type-guard","validationCode":"raise TypeError, 'stages must be callables' unless stages.all? { |s| s.respond_to?(:call) }","typeGuard":"def stage_callable?(stage)\n  stage.respond_to?(:call)\nend","tryCatchPattern":"begin\n  out = pipeline(items, *stages)\nrescue TypeError => e\n  raise unless e.message.include?('stages must be callables')\n  out = pipeline(items, *stages.map { |s| s.is_a?(Symbol) ? ->(x) { x.public_send(s) } : s })\nend","preventionTips":["Pass `method(:name)` or lambdas as stages — never Symbols or Strings.","Keep stage functions as Proc objects from creation time.","Validate stage list with `respond_to?(:call)` before composing pipelines."],"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"}