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
- Add both mandatory keys: Rack::Attack.throttle('req/ip', limit: 100, period: 60) { |req| req.ip }.
- For ENV-driven config, coerce with defaults: limit: Integer(ENV.fetch('RATE_LIMIT', '100')), period: Integer(ENV.fetch('RATE_PERIOD', '60')).
- Check key spelling - the API uses exactly :limit and :period (:period may be a lambda/proc of the request).
- 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
- Add an initializer spec that loads the whole rack-attack config - missing limit/period then fails CI at boot, not production.
- Use Integer(ENV.fetch(..., default)) for numeric options so nil never reaches Throttle#initialize.
- Remember period may be a proc (period: ->(req) { ... }); anything nil/false raises even if the key exists.
- Copy option names exactly (:limit, :period) when porting rules from other rate-limiting gems.
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
- Must pass findtime option
- Must pass bantime option
- Must pass maxretry option
- Rack::Attack::MissingStoreError
AI-assisted analysis of rack/rack-attack@b771ea18af (2026-08-21).
Data as JSON: /api/errors/5ba474b0660b6c0a.
Report an issue: GitHub.