rack/rack-attack · error · ArgumentError

Must pass #{opt.inspect} option

Error message

Must pass #{opt.inspect} option

What it means

Rack::Attack::Throttle#initialize (the object behind Rack::Attack.throttle(name, options, &block)) enforces MANDATORY_OPTIONS = [:limit, :period] with 'raise ArgumentError, "Must pass #{opt.inspect} option" unless options[opt]'. :limit is the max requests per window and :period the window length in seconds (or a callable). The message interpolates the missing symbol, so in practice you see 'Must pass :limit option' or 'Must pass :period option'. The guard is truthiness-based, so nil/false values raise as well (0 is truthy in Ruby and passes).

Source

Thrown at lib/rack/attack/throttle.rb:14

# frozen_string_literal: true

module Rack
  class Attack
    class Throttle
      MANDATORY_OPTIONS = [:limit, :period].freeze

      attr_reader :name, :limit, :period, :block, :type

      def initialize(name, options, &block)
        @name = name
        @block = block
        MANDATORY_OPTIONS.each do |opt|
          raise ArgumentError, "Must pass #{opt.inspect} option" unless options[opt]
        end
        @limit = options[:limit]
        @period = options[:period].respond_to?(:call) ? options[:period] : options[:period].to_i
        @type   = options.fetch(:type, :throttle)
      end

      def cache
        Rack::Attack.cache
      end

      def matched_by?(request)
        discriminator = discriminator_for(request)
        return false unless discriminator

        current_period  = period_for(request)
        current_limit   = limit_for(request)
        count           = cache.count("#{name}:#{discriminator}", current_period)

View on GitHub (pinned to b771ea18af)

Solutions

  1. Add both mandatory keys: Rack::Attack.throttle('req/ip', limit: 100, period: 60) { |req| req.ip }.
  2. For ENV-driven config, coerce with defaults: limit: Integer(ENV.fetch('RATE_LIMIT', '100')), period: Integer(ENV.fetch('RATE_PERIOD', '60')).
  3. Check key spelling - the API uses exactly :limit and :period (:period may be a lambda/proc of the request).
  4. If wrapping throttle in your own DSL, validate the options hash before delegating so the error names your wrapper, not rack-attack internals.

Example fix

# before
Rack::Attack.throttle('logins per ip') do |req|
  req.ip if req.path == '/login'
end
# => ArgumentError: Must pass :limit option (then :period)

# after
Rack::Attack.throttle('logins per ip', limit: 5, period: 60) do |req|
  req.ip if req.path == '/login'
end
Defensive patterns

Strategy: validation

Validate before calling

def build_throttle(name, opts, &block)
  missing = %i[limit period].reject { |k| opts[k] }
  raise ArgumentError, "throttle '#{name}' missing options: #{missing.inspect}" unless missing.empty?
  Rack::Attack.throttle(name, opts, &block)
end

build_throttle('req/ip', limit: Integer(ENV.fetch('RATE_LIMIT', '100')), period: 60) { |req| req.ip }

Type guard

def valid_throttle_options?(opts)
  opts.is_a?(Hash) &&
    opts[:limit].is_a?(Integer) && opts[:limit].positive? &&
    (opts[:period].is_a?(Integer) || opts[:period].respond_to?(:call))
end

Try / catch

begin
  Rack::Attack.throttle(name, opts, &block)
rescue ArgumentError => e
  raise unless e.message =~ /Must pass :(limit|period) option/
  abort "rack-attack config error in '#{name}': #{e.message}" # fail boot loudly
end

Prevention

When it happens

Trigger: Rack::Attack.throttle('req/ip') { |req| req.ip } with no options hash, or hashes missing either key: throttle('logins', period: 60) {...} (no limit), throttle('api', limit: 10) {...} (no period). Also limit: nil / period: nil from ENV or YAML config, and misspelled keys (rate:, window:, per:). Raises once at Rails boot / rackup when the initializer defines the throttle, before any request is served.

Common situations: First-time setup following a throttle example and dropping a line; extracting limits to ENV/config (ENV['RATE_LIMIT'] unset -> nil); renaming keys during a port from Rack::Ratelimit or redis-throttle whose option names differ (requests:, seconds:); upgrading rack-attack across majors where option names were standardized; dynamic rule DSLs that build options hashes conditionally.

Related errors


AI-assisted analysis of rack/rack-attack@b771ea18af (2026-08-21). Data as JSON: /api/errors/5ba474b0660b6c0a. Report an issue: GitHub.