ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
Concurrent::ScheduledTask executes a block after a delay; the block passed to ScheduledTask.new (or ScheduledTask.execute) is the task body and there is no option-based alternative for supplying it. The constructor raises ArgumentError('no block given') immediately when no block is attached. The task cannot be provided via opts (e.g. opts[:task] is ignored), only via the &block parameter.
Source
Thrown at lib/concurrent-ruby/concurrent/scheduled_task.rb:179
# The executor on which to execute the task.
# @!visibility private
attr_reader :executor
# Schedule a task for execution at a specified future time.
#
# @param [Float] delay the number of seconds to wait for before executing the task
#
# @yield the task to be performed
#
# @!macro executor_and_deref_options
#
# @option opts [object, Array] :args zero or more arguments to be passed the task
# block on execution
#
# @raise [ArgumentError] When no block is given
# @raise [ArgumentError] When given a time that is in the past
def initialize(delay, opts = {}, &task)
raise ArgumentError.new('no block given') unless block_given?
raise ArgumentError.new('seconds must be greater than zero') if delay.to_f < 0.0
super(NULL, opts, &nil)
synchronize do
ns_set_state(:unscheduled)
@parent = opts.fetch(:timer_set, Concurrent.global_timer_set)
@args = get_arguments_from(opts)
@delay = delay.to_f
@task = task
@time = nil
@executor = Options.executor_from_options(opts) || Concurrent.global_io_executor
self.observers = Collection::CopyOnNotifyObserverSet.new
end
end
# The `delay` value given at instantiation.
#View on GitHub (pinned to 0b88d5ff75)
Solutions
- Attach the task as a block: Concurrent::ScheduledTask.new(3) { do_work }
- When the callable is a variable, forward it explicitly: Concurrent::ScheduledTask.new(3, &my_callable)
- If you only need fire-and-forget delayed execution, use the convenience wrapper: Concurrent.schedule(3) { do_work }
- Guard construction sites: raise your own descriptive error when the callable is nil so failures surface at the call site, not inside the gem
Example fix
# before
task = Concurrent::ScheduledTask.new(3)
# after
task = Concurrent::ScheduledTask.new(3) { cleanup_temp_files }
# with a stored callable
task = Concurrent::ScheduledTask.new(3, &handler) Defensive patterns
Strategy: validation
Validate before calling
def build_scheduled_task(delay, &task) raise ArgumentError, 'a task block/callable is required' if task.nil? Concurrent::ScheduledTask.new(delay, &task) end
Type guard
def task_callable?(obj) obj.respond_to?(:call) end
Try / catch
begin
Concurrent::ScheduledTask.new(delay) { work }
rescue ArgumentError => e
raise ContextError, "ScheduledTask for #{job_name}: #{e.message}" if e.message == 'no block given'
raise
end Prevention
- Always construct ScheduledTask with a literal block or &callable at the same call site
- In builder methods, take &block and forward it with & so nil bodies surface as your own clear error
- Add a one-line spec asserting your factory raises a helpful error when the task is missing
When it happens
Trigger: Calling Concurrent::ScheduledTask.new(5) or ScheduledTask.execute(5) with no block; storing the task in a local variable and calling new(delay) without forwarding it with &task; refactoring that turns the literal block into a conditional that sometimes yields none.
Common situations: Porting code from Thread.new or TimerTask where the callable was passed differently; building tasks dynamically from config where the proc may be missing; passing a lambda as a positional or opts argument instead of as a block.
Related errors
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/293496067799dd18.
Report an issue: GitHub.