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 : @Pid

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Define the environment as a class: `class MyEnv < Concurrent::ErlangActor::Environment; ...; end` and pass `MyEnv` itself (not an instance).
  2. Or keep the callbacks in a plain Module and pass the module — it is extended onto a default Environment instance.
  3. 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.
  4. 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

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


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/2c5e72fc68362a2d. Report an issue: GitHub.