ruby-concurrency/concurrent-ruby · error · ArgumentError
should pass observer as a first argument or block
Error message
should pass observer as a first argument or block
What it means
`CopyOnWriteObserverSet#add_observer(observer = nil, func = :update, &block)` demands either an observer object or a block; called with neither it raises ArgumentError immediately. It is the copy-on-write twin of CopyOnNotifyObserverSet — identical validation, different mutation strategy (it duplicates the observer hash on each add so iteration never locks) — and both back Concurrent::Observable, so the same misuse surfaces from any observable class. Supplying both observer and block is the mirrored error.
Source
Thrown at lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb:21
module Concurrent
module Collection
# A thread safe observer set implemented using copy-on-write approach:
# every time an observer is added or removed the whole internal data structure is
# duplicated and replaced with a new one.
#
# @api private
class CopyOnWriteObserverSet < Synchronization::LockableObject
def initialize
super()
synchronize { ns_initialize }
end
# @!macro observable_add_observer
def add_observer(observer = nil, func = :update, &block)
if observer.nil? && block.nil?
raise ArgumentError, 'should pass observer as a first argument or block'
elsif observer && block
raise ArgumentError.new('cannot provide both an observer and a block')
end
if block
observer = block
func = :call
end
synchronize do
new_observers = @observers.dup
new_observers[observer] = func
@observers = new_observers
observer
end
end
# @!macro observable_delete_observerView on GitHub (pinned to 0b88d5ff75)
Solutions
- Pass a real observer object: `add_observer(MyObserver.new)`.
- Or pass a block: `add_observer { |time, value| react(value) }`.
- Guard at the boundary: `add_observer(obs) if obs` or `obs ||= NOOP_OBSERVER` before calling.
- Add a presence check in your registration helper so the failure names the missing callback.
Example fix
// before registry.add_observer(options[:listener]) # options[:listener] is nil // after listener = options[:listener] || NOOP_LISTENER registry.add_observer(listener)
Defensive patterns
Strategy: validation
Validate before calling
def register(set, observer) raise ArgumentError, 'observer required' unless observer set.add_observer(observer) end register(set, options[:listener] || NOOP_LISTENER)
Type guard
def observer_supplied?(observer = nil, &block) !observer.nil? || !block.nil? end
Try / catch
begin
set.add_observer(obs)
rescue ArgumentError => e
raise unless e.message.include?('observer as a first argument')
set.add_observer(&DEFAULT_HANDLER)
end Prevention
- Default or require the listener before registration.
- Keep nil out of the pipeline: normalize at the config boundary.
- Decide up front between object form (func callback) and block form; do not mix.
When it happens
Trigger: `observable.add_observer` with no args; `add_observer(nil)`; passing an unset variable (`add_observer(callback)` where `callback` stayed nil). Legal: `add_observer(obs)`, `add_observer(obs, :on_change)`, `add_observer { |*a| ... }`.
Common situations: Optional listener registration driven by config hashes with absent keys; DSL builders that attach observers before constructing them; nil propagated from `params[:observer]` or service lookups.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- should pass observer as a first argument or block
- cannot provide both an observer and a block
- cannot give arguments and a block
- cannot provide both an observer and a block
- cannot give arguments and a block
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/342babed0e7a7eeb.
Report an issue: GitHub.