ruby-concurrency/concurrent-ruby · error · ArgumentError

probe is not Resolvable

Error message

probe is not Resolvable

What it means

`Ask` is the internal signal ErlangActor uses for request/response: it pairs the caller's message with a `probe` future that gets resolved with the reply. Its constructor enforces `probe.is_a?(Concurrent::Promises::Resolvable)` and raises ArgumentError otherwise. The built-in `ask`/`ask_op` paths construct the probe for you (`ask_op(message, probe = Promises.resolvable_future)`), so hitting this error means a probe of the wrong kind was threaded through — typically a plain `Promises.future` (which is resolvable-scheduled, not a Resolvable) or a custom future object.

Source

Thrown at lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb:1264

      end
    end

    private_constant :OnThread

    class AbstractSignal < Synchronization::Object
      safe_initialization!
    end

    private_constant :AbstractSignal

    class Ask < AbstractSignal
      attr_reader :message, :probe

      def initialize(message, probe)
        super()
        @message = message
        @probe   = probe
        raise ArgumentError, 'probe is not Resolvable' unless probe.is_a? Promises::Resolvable
      end
    end

    private_constant :Ask

    module HasFrom

      # @return [Pid]
      attr_reader :from

      # @!visibility private
      def initialize(from)
        # noinspection RubySuperCallWithoutSuperclassInspection
        super()
        @from = from
      end

      # @return [true, false]

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Use the public `pid.ask(message)` / `pid.ask!` which create a `resolvable_future` probe internally.
  2. If you call `ask_op` with your own probe, create it with `Concurrent::Promises.resolvable_future` — that object satisfies Promises::Resolvable.
  3. Bridge custom futures: resolve a `resolvable_future` when your custom future completes, and pass the resolvable as the probe.
  4. Treat Ask and other private constants as off-limits; go through Pid's public API.

Example fix

// before
probe = Concurrent::Promises.future { compute } # not Resolvable
pid.ask_op(message, probe)

// after
probe = Concurrent::Promises.resolvable_future # a Promises::Resolvable
pid.ask_op(message, probe)
Defensive patterns

Strategy: type-guard

Validate before calling

probe = Concurrent::Promises.resolvable_future
raise ArgumentError, 'probe is not Resolvable' unless resolvable_probe?(probe)
pid.ask_op(message, probe)

Type guard

def resolvable_probe?(obj)
  obj.is_a?(Concurrent::Promises::Resolvable)
end

Prevention

When it happens

Trigger: `pid.ask_op(message, Concurrent::Promises.future { compute })` — a plain future is not a Promises::Resolvable. Monkey-patching or reimplementing ask and passing a Task, an `Edge::Future`, or any custom future class as the probe. Constructing Ask-style signals manually against the internal protocol (Ask itself is a private_constant).

Common situations: Substituting another future implementation for the probe; copying library internals into app code during upgrades; version drift where the resolvable contract on Promises objects changed between edge releases.

Related errors


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