ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
BlockedTaskPromise (promises.rb:1723) is the base class of the task-carrying chain steps — ThenPromise, RescuePromise, EnsurePromise, created by Future#then, #rescue, and #ensure. Each requires a block; building the chain step without one raises ArgumentError 'no block given' immediately at chain-construction time, before anything executes.
Source
Thrown at lib/concurrent-ruby/concurrent/promises.rb:1724
# @return [true,false] if resolvable
def resolvable?(countdown, future, index)
countdown.zero?
end
def process_on_blocker_resolution(future, index)
@Countdown.decrement
end
def on_resolvable(resolved_future, index)
raise NotImplementedError
end
end
# @abstract
class BlockedTaskPromise < BlockedPromise
def initialize(delayed, blockers_count, default_executor, executor, args, &task)
raise ArgumentError, 'no block given' unless block_given?
super delayed, 1, Future.new(self, default_executor)
@Executor = executor
@Task = task
@Args = args
end
def executor
@Executor
end
end
class ThenPromise < BlockedTaskPromise
private
def initialize(delayed, blockers_count, default_executor, executor, args, &task)
super delayed, blockers_count, default_executor, executor, args, &task
end
View on GitHub (pinned to 0b88d5ff75)
Solutions
- Supply the transformation block: f.then { |v| ... }.
- For an intentional no-op pass-through use f.then { |v| v }.
- If you only want to trigger/await execution, use f.wait, f.value, or f.run instead of then.
Example fix
# before
f2 = f.then # ArgumentError: no block given
# after
f2 = f.then { |v| v } # explicit pass-through Defensive patterns
Strategy: validation
Validate before calling
def then_or_noop(future, &block)
block ? future.then(&block) : future.then { |v| v }
end Prevention
- then, rescue, and ensure always require a block; lint for bare calls.
- Use f.then { |v| v } for intentional pass-through.
- Use f.wait/f.value/f.run when you only need to trigger evaluation, not transform.
When it happens
Trigger: future.then with no block (missing { }); future.rescue without a block when the author only wanted failure propagation; conditional chaining like cond ? f.then { |v| v } : f.then where one branch lost its block.
Common situations: Refactors that delete or move block bodies; intending a no-op pass-through; assuming then merely schedules execution of the upstream future.
Related errors
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/107613e0487e8332.
Report an issue: GitHub.