ruby-concurrency/concurrent-ruby · error · ArgumentError
environment has to be a class inheriting from Environment or
Error message
environment has to be a class inheriting from Environment or a module
What it means
An Edge ErlangActor gets its message-handling behaviour from an `environment:` argument (keyword on `Concurrent::ErlangActor.spawn`, default `ErlangActor::Environment`). AbstractActor#initialize accepts exactly two shapes: a Class inheriting from ErlangActor::Environment (instantiated with `(actor, executor)`), or a Module that is `extend`ed onto a fresh Environment. Anything else — an instance, String, Symbol, or unrelated object — raises this ArgumentError at spawn time, before any message is processed.
Source
Thrown at lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb:671
super()
@Mailbox = mailbox
@Pid = Pid.new self, name
@Linked = ::Set.new
@Monitors = {}
@Monitoring = {}
@MonitoringLateDelivery = {}
@Terminated = Promises.resolvable_future
@trap = false
@reply = nil
@Environment = if environment.is_a?(Class) && environment <= Environment
environment.new self, executor
elsif environment.is_a? Module
e = Environment.new self, executor
e.extend environment
e
else
raise ArgumentError,
"environment has to be a class inheriting from Environment or a module"
end
end
def tell_op(message)
log DEBUG, @Pid, told: message
if (mailbox = @Mailbox)
mailbox.push_op(message).then { @Pid }
else
Promises.fulfilled_future @Pid
end
end
def tell(message, timeout = nil)
log DEBUG, @Pid, told: message
if (mailbox = @Mailbox)
timed_out = mailbox.push message, timeout
timeout ? timed_out : @PidView on GitHub (pinned to 0b88d5ff75)
Solutions
- Define the environment as a class: `class MyEnv < Concurrent::ErlangActor::Environment; ...; end` and pass `MyEnv` itself (not an instance).
- Or keep the callbacks in a plain Module and pass the module — it is extended onto a default Environment instance.
- If you already hold an instance, pass its class only when it subclasses Environment; otherwise refactor the behaviour into one of the two supported shapes.
- When the environment comes from config, constantize and type-check it at boot, not at spawn time.
Example fix
// before class MyEnv def on_message(msg); reply msg * 2; end end Concurrent::ErlangActor.spawn(:on_thread, environment: MyEnv.new) // after class MyEnv < Concurrent::ErlangActor::Environment def on_message(msg); reply msg * 2; end end Concurrent::ErlangActor.spawn(:on_thread, environment: MyEnv)
Defensive patterns
Strategy: type-guard
Validate before calling
raise ArgumentError, 'environment must be an Environment subclass or Module' unless erlang_environment?(env)
Concurrent::ErlangActor.spawn(:on_thread, environment: env) { ... } Type guard
def erlang_environment?(env) (env.is_a?(Class) && env <= Concurrent::ErlangActor::Environment) || env.is_a?(Module) end
Prevention
- Define environments as Environment subclasses or plain modules — never instances.
- Funnel actor creation through one factory that type-checks the environment.
- Add a boot-time smoke test spawning one actor per environment class.
When it happens
Trigger: `Concurrent::ErlangActor.spawn(:on_thread, environment: MyEnv.new)` — an instance instead of the class. Passing a String/Symbol constant name (`environment: 'MyEnv'`). Passing a class that does not subclass ErlangActor::Environment (duck-typed behaviour class). Works: `environment: MyEnv` where `MyEnv < ErlangActor::Environment`, or `environment: MyBehaviourModule` (any Module).
Common situations: Porting Erlang/Elixir GenServer code where you naturally have behaviour instances; forgetting `< ErlangActor::Environment` when defining the environment class; environment selected from config/strings and passed through without constantizing.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- only one of block or value can be supplied
- bad options #{options}
- probe is not Resolvable
- :class option is ignored when calling on context class, use
- unbuffered channels cannot have a capacity
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/2c5e72fc68362a2d.
Report an issue: GitHub.