instructure/canvas-lms · error · ArgumentError

Rate limit must be either nil, or hash with :limit and…

Error message

Rate limit must be either nil, or hash with :limit and :period keys

What it means

LlmConfig#validate! raises ArgumentError unless rate_limit is nil or exactly a Hash with keys [:limit, :period] (in that order per keys == %i[limit period]). Any other shape — extra keys, missing keys, wrong types, or a different key order — is rejected.

Solutions

  1. Pass exactly {limit: <int>, period: <symbol>} or nil.
  2. If loading from YAML, symbolize keys and reorder/normalize to [:limit, :period].
  3. Remove unsupported keys from the rate_limit hash.

Example fix

// before
LlmConfig.new("gpt4", "gpt-4", {"limit" => 10, "period" => :minute}, template, {})
// after
LlmConfig.new("gpt4", "gpt-4", {limit: 10, period: :minute}, template, {})
Defensive patterns

Strategy: validation

Validate before calling

ok = rate_limit.nil? || (rate_limit.is_a?(Hash) && rate_limit.keys == %i[limit period])
raise ArgumentError, "bad rate_limit shape" unless ok

Type guard

def valid_rate_limit?(rl) = rl.nil? || (rl.is_a?(Hash) && rl.keys == %i[limit period])

Try / catch

begin
  cfg = LlmConfig.new(name, model_id, rate_limit, ...)
rescue ArgumentError => e
  Rails.logger.error("Invalid rate_limit: #{e.message}")
  raise
end

Prevention

When it happens

Trigger: LlmConfig.new(name, model_id, rate_limit, ...) where rate_limit is a string, a hash with keys like {limit: n} only, keys in a different order, or containing extra keys.

Common situations: Config parsed from YAML where keys load as strings ('limit' vs :limit); adding new rate-limit fields the validator doesn't know; constructing the hash in a different key order.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/83d240111c1f0cab. Report an issue: GitHub.

Appendix: source

Thrown at app/models/llm_config.rb:65

        new_options[key] = value.gsub("<#{placeholder_prefix}_PLACEHOLDER>", sub_value.to_s) if value.is_a?(String)
      end
    end

    new_options.each_value do |value|
      if value.is_a?(String) && (remaining_placeholder = value.match(/<\w+_PLACEHOLDER>/))
        raise ArgumentError, "Options still contain placeholder: #{remaining_placeholder[0]}"
      end
    end

    [new_template, new_options]
  end

  private

  def validate!
    raise ArgumentError, "Name must be a string" unless @name.is_a?(String)
    raise ArgumentError, "Model ID must be a string" unless @model_id.is_a?(String)
    raise ArgumentError, "Rate limit must be either nil, or hash with :limit and :period keys" unless @rate_limit.nil? || (@rate_limit.is_a?(Hash) && @rate_limit.keys == %i[limit period])
    raise ArgumentError, "Template must be a string" unless @template.is_a?(String)
    raise ArgumentError, "Options must be a hash" unless @options.is_a?(Hash)
  end
end

View on GitHub (pinned to 1c9f0bb801)