ruby-concurrency/concurrent-ruby · error · ArgumentError
only one error handler allowed
Error message
only one error handler allowed
What it means
A Channel selector stores exactly one error handler (@error_handler), because there is no defined order in which multiple handlers could run. Registering a second one via sel.error raises ArgumentError immediately.
Source
Thrown at lib/concurrent-ruby-edge/concurrent/channel/selector.rb:52
def put(channel, message, &block)
@clauses << PutClause.new(channel, message, block)
end
alias_method :send, :put
def after(seconds, &block)
@clauses << AfterClause.new(seconds, block)
end
alias_method :timeout, :after
def default(&block)
raise ArgumentError.new('no block given') unless block_given?
@clauses << DefaultClause.new(block)
end
def error(&block)
raise ArgumentError.new('no block given') unless block_given?
raise ArgumentError.new('only one error handler allowed') if @error_handler
@error_handler = block
end
def execute
raise Channel::Error.new('no clauses given') if @clauses.empty?
loop do
done = @clauses.each do |clause|
result = clause.execute
break result if result.just?
end
break done.value if done.is_a?(Concurrent::Maybe)
Thread.pass
end
rescue => ex
if @error_handler
@error_handler.call(ex)
else
raise exView on GitHub (pinned to 0b88d5ff75)
Solutions
- Keep exactly one sel.error per select and compose behavior inside it: sel.error { |e| log(e); report(e) if e.respond_to?(:critical?) && e.critical? }.
- If a shared helper registers error handling, let call sites pass their extra handler into the helper rather than calling sel.error again.
- Search your select-building code paths for all sel.error call sites before combining helpers.
Example fix
# before
sel.error { |e| log(e) }
sel.error { |e| report(e) } # raises: only one error handler allowed
# after
sel.error { |e|
log(e)
report(e)
} Defensive patterns
Strategy: validation
Validate before calling
def build_selector(handlers = {})
Concurrent::Channel.select do |sel|
yield sel
sel.error(&handlers.fetch(:on_error, DEFAULT_ERROR_HANDLER)) unless sel.instance_variable_get(:@error_handler)
end
end Prevention
- Exactly one sel.error per select - compose multiple behaviors inside a single block.
- If a shared helper installs error handling, do not also call sel.error at call sites; pass extra handlers into the helper instead.
- Grep for sel.error across helper + call-site before combining select templates.
When it happens
Trigger: Calling sel.error twice within one Channel.select block - most often a shared 'register common clauses' helper that adds a standard error handler combined with an inline sel.error at the call site.
Common situations: DSL helper methods that install default error handling (sel.error { log(e) }) plus per-use-site handlers; copy-pasting a select template that already contains sel.error onto one that gets another via a wrapper.
Related errors
- invalid action
- no block given
- timeout must 0.0 or more
- unbuffered channels cannot have a capacity
- capacity must be at least 1 for this buffer type
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/23361e9b7784b6ca.
Report an issue: GitHub.