ruby-concurrency/concurrent-ruby · error · ArgumentError

ArgumentError

Error message

ArgumentError

What it means

Future#exception(*args) follows Exception#exception semantics: at most one optional argument (a message) is allowed. After the rejected? precondition, promises.rb:1016 raises a bare ArgumentError when args.size > 1. Only a rejected future can reach this line, since the state check at :1015 runs first.

Source

Thrown at lib/concurrent-ruby/concurrent/promises.rb:1016

      def value!(timeout = nil, timeout_value = nil)
        if wait_until_resolved! timeout
          internal_state.value
        else
          timeout_value
        end
      end

      # Allows rejected Future to be risen with `raise` method.
      # If the reason is not an exception `Runtime.new(reason)` is returned.
      #
      # @example
      #   raise Promises.rejected_future(StandardError.new("boom"))
      #   raise Promises.rejected_future("or just boom")
      # @raise [Concurrent::Error] when raising not rejected future
      # @return [Exception]
      def exception(*args)
        raise Concurrent::Error, 'it is not rejected' unless rejected?
        raise ArgumentError unless args.size <= 1
        reason = Array(internal_state.reason).flatten.compact
        callsites = SET_BACKTRACE_LOCATIONS_SUPPORTED ? caller_locations : caller
        if reason.size > 1
          ex = Concurrent::MultipleErrors.new reason
          ex.set_backtrace(callsites)
          ex
        else
          ex = if reason[0].respond_to? :exception
                 reason[0].exception(*args)
               else
                 RuntimeError.new(reason[0]).exception(*args)
               end
          if SET_BACKTRACE_LOCATIONS_SUPPORTED && (locations = ex.backtrace_locations)
            ex.set_backtrace locations + callsites
          else
            ex.set_backtrace Array(ex.backtrace) + callsites.map(&:to_s)
          end
          ex

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Pass at most one argument: future.exception('boom').
  2. Truncate splats defensively: future.exception(*args.take(1)).
  3. To attach a backtrace, call .set_backtrace on the returned exception instead of passing it as an argument.

Example fix

# before
ex = future.exception('boom', caller) # ArgumentError: 2 args

# after
ex = future.exception('boom')
ex.set_backtrace(caller) if ex
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'at most one argument' if args.size > 1
future.exception(*args.take(1))

Prevention

When it happens

Trigger: future.exception('boom', caller) with two arguments; splatting caller-supplied arguments through: future.exception(*args) where args has 2+ elements.

Common situations: Generic exception-building helpers that pass extra context (message + backtrace) into .exception; porting exception-construction patterns from other libraries that accept a backtrace argument.

Related errors


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