{"record":{"id":"79c0a35c7bd28fe8","repo":"ruby-concurrency/concurrent-ruby","slug":"no-block-given-79c0a3","errorCode":null,"errorMessage":"no block given","messagePattern":"no block given","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb","lineNumber":28,"sourceCode":"  # immediately runs every `#post` operation on a new thread, blocking the\n  # current thread until the operation is complete. This is similar to how the\n  # ImmediateExecutor works, but the operation has the full stack of the new\n  # thread at its disposal. This can be helpful when the operations will spawn\n  # more operations on the same executor and so on - such a situation might\n  # overflow the single stack in case of an ImmediateExecutor, which is\n  # inconsistent with how it would behave for a threaded executor.\n  #\n  # @note Intended for use primarily in testing and debugging.\n  class IndirectImmediateExecutor < ImmediateExecutor\n    # Creates a new executor\n    def initialize\n      super\n      @internal_executor = SimpleExecutorService.new\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\n      event = Concurrent::Event.new\n      @internal_executor.post do\n        begin\n          task.call(*args)\n        ensure\n          event.set\n        end\n      end\n      event.wait\n\n      true\n    end\n  end\nend\n","sourceCodeStart":10,"sourceCodeEnd":45,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb#L10-L45","documentation":"IndirectImmediateExecutor is an ImmediateExecutor that runs the task on an internal SimpleExecutorService thread and blocks the caller on an Event until it finishes, avoiding stack growth from nested immediate execution. post(*args, &task) raises ArgumentError('no block given') when the block is missing.","triggerScenarios":"executor.post with no block; passing args only (executor.post(:job)); forwarding a stored proc without the & sigil.","commonSituations":"Testing harnesses where a callable is optional and nil slips through; refactoring an executor-swapping layer (tests use IndirectImmediateExecutor, production uses a real pool) that drops the block on one path.","solutions":["Always call with a block: executor.post { do_work }","Pass stored callables with &: executor.post(&task)","Check task.respond_to?(:call) before posting in generic dispatch code"],"exampleFix":"# before\nexecutor.post(:arg)\n# after\nexecutor.post(:arg) { |a| do_work(a) }","handlingStrategy":"validation","validationCode":"raise ArgumentError, 'task must respond to #call' unless task.respond_to?(:call)\nexecutor.post(&task)","typeGuard":null,"tryCatchPattern":"begin\n  executor.post(:arg) { |a| run(a) }\nrescue ArgumentError => e\n  raise unless e.message == 'no block given'\n  raise ArgumentError, 'post requires a block'\nend","preventionTips":["Pass blocks, not positional procs, when posting","Keep executor-swapping test layers block-transparent (forward &block)","Validate callables at the boundary of generic dispatch code"],"tags":["ruby","concurrency","executor","argument-error","missing-block"],"backgroundTag":"missing-block-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}