{"record":{"id":"5133917d2906b367","repo":"ruby-concurrency/concurrent-ruby","slug":"rescuers-and-block-are-both-missing","errorCode":null,"errorMessage":"rescuers and block are both missing","messagePattern":"rescuers and block are both missing","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/promise.rb","lineNumber":324,"sourceCode":"    #     promise is rejected.\n    #   @param [ThreadPool] executor An optional thread pool executor to be used\n    #     in the new Promise\n    # @overload then(rescuer, executor: executor, &block)\n    #   @param [Proc] rescuer An optional rescue block to be executed if the\n    #     promise is rejected.\n    #   @param [ThreadPool] executor An optional thread pool executor to be used\n    #     in the new Promise\n    def then(*args, &block)\n      if args.last.is_a?(::Hash)\n        executor = args.pop[:executor]\n        rescuer = args.first\n      else\n        rescuer, executor = args\n      end\n\n      executor ||= @executor\n\n      raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?\n      block = Proc.new { |result| result } unless block_given?\n      child = Promise.new(\n        parent: self,\n        executor: executor,\n        on_fulfill: block,\n        on_reject: rescuer\n      )\n\n      synchronize do\n        child.state = :pending if @state == :pending\n        child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled\n        child.on_reject(@reason) if @state == :rejected\n        @children << child\n      end\n\n      child\n    end\n","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/promise.rb#L306-L342","documentation":"Promise#then builds a child promise whose fulfillment callback is either the given block or a rescuer callable given as the first positional argument (optionally an executor as second argument or via an options hash). If neither a rescuer nor a block is supplied, there is no callback to chain, so it raises ArgumentError 'rescuers and block are both missing'.","triggerScenarios":"Bare promise.then; promise.then(executor: my_pool) — the options hash is consumed for the executor and no callback remains, which still raises; passing only args that are meant for the block before the block is written.","commonSituations":"Starting a chain and forgetting the callback mid-edit; passing executor options intending to configure the child without yet adding logic; refactoring then { |v| v }.then(...) chains down to a bare then.","solutions":["Give then a block: promise.then { |v| v + 1 }","Or a rescuer callable: promise.then(->(reason) { handle(reason) })","When specifying an executor, still supply the callback: promise.then(executor: pool) { |v| v + 1 }"],"exampleFix":"# before\nchild = promise.then(executor: pool)\n\n# after\nchild = promise.then(executor: pool) { |v| v + 1 }","handlingStrategy":"validation","validationCode":"raise ArgumentError, 'callback block or rescuer required' unless block_given? || rescuer\npromise.then(on_fulfill_options) { |v| yield(v) }","typeGuard":"->(obj) { obj.respond_to?(:call) } # valid rescuer for then(rescuer)","tryCatchPattern":"begin\n  promise.then(executor: executor, &callback)\nrescue ArgumentError => e\n  raise ArgumentError, 'then needs a block or rescuer' if /rescuers and block/.match?(e.message)\n  raise\nend","preventionTips":["Always chain then with a callback; executor options alone are not enough","When refactoring chains, delete then calls whose callback was removed rather than leaving them bare","Remember then(rescuer, executor) positional order or use the executor: hash form plus a block"],"tags":["ruby","concurrency","promise","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"}