{"record":{"id":"ea2b06c8ca388cfb","repo":"ruby-concurrency/concurrent-ruby","slug":"no-block-given-ea2b06","errorCode":null,"errorMessage":"no block given","messagePattern":"no block given","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb","lineNumber":23,"sourceCode":"module Concurrent\n\n  # A wrapper/delegator for any `ExecutorService` that\n  # guarantees serialized execution of tasks.\n  #\n  # @see [SimpleDelegator](http://www.ruby-doc.org/stdlib-2.1.2/libdoc/delegate/rdoc/SimpleDelegator.html)\n  # @see Concurrent::SerializedExecution\n  class SerializedExecutionDelegator < SimpleDelegator\n    include SerialExecutorService\n\n    def initialize(executor)\n      @executor   = executor\n      @serializer = SerializedExecution.new\n      super(executor)\n    end\n\n    # @!macro executor_service_method_post\n    def post(*args, &task)\n      raise ArgumentError.new('no block given') unless block_given?\n      return false unless running?\n      @serializer.post(@executor, *args, &task)\n    end\n  end\nend\n","sourceCodeStart":5,"sourceCodeEnd":29,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb#L5-L29","documentation":"SerializedExecutionDelegator wraps another executor so that submitted tasks run one at a time in submission order; its #post requires the task as a block. If you call post without a block (for example passing a Proc or Method as a positional argument), it raises ArgumentError 'no block given' before anything is queued.","triggerScenarios":"delegator.post(task) where task is a Proc/Method instead of delegator.post(&task); forwarding a block through a wrapper method that forgets the &; calling post on an executor obtained from SerializedExecutionDelegator.new(executor) with the task in a variable.","commonSituations":"Wrapping a custom or global executor for serialized (one-at-a-time) execution and reusing callable objects built elsewhere; refactoring code that stored lambdas in variables; test doubles that call post with positional callables.","solutions":["Pass the task as a block: delegator.post { do_work }","If the task is a Proc/Method object, splat it as a block: delegator.post(&task)","In wrapper methods, forward the block explicitly: def enqueue(&task) delegator.post(&task) end"],"exampleFix":"# before\ntask = ->(arg) { process(arg) }\ndelegator.post(task)\n\n# after\ntask = ->(arg) { process(arg) }\ndelegator.post(&task)","handlingStrategy":"validation","validationCode":"def enqueue(serializer, &task)\n  raise ArgumentError, 'task block required' unless task\n  serializer.post(&task)\nend","typeGuard":"->(obj) { obj.respond_to?(:call) } # then post(&obj)","tryCatchPattern":"begin\n  delegator.post(&task)\nrescue ArgumentError => e\n  raise ArgumentError, \"enqueue rejected: #{e.message}\" if /no block/.match?(e.message)\n  raise\nend","preventionTips":["Never pass callables positionally to executor APIs — convert with & at the call site","In wrapper/forwarder methods always declare (&task) explicitly so blocks survive the hop","Add a unit test that posts a stored callable through every wrapper you own"],"tags":["ruby","concurrency","executor","block-argument","argument-validation"],"backgroundTag":"missing-block-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}