bblimke/webmock · error · ArgumentError

Key was repeated: #{key.inspect}

Error message

Key was repeated: #{key.inspect}

What it means

With notation :flat, QueryMapper maps each query-string key to a single scalar value; a repeated key (a=1&a=2) cannot be represented in that shape, so fill_accumulator_for_flat raises ArgumentError naming the duplicated key. The query string itself is legal - this is a data-shape conflict between the input and the chosen notation, not a parse failure.

Source

Thrown at lib/webmock/util/query_mapper.rb:97

      end

      def collect_query_hash(query_array, empty_accumulator, options)
        query_array.compact.inject(empty_accumulator.dup) do |accumulator, (key, value)|
          value = if value.nil?
                    nil
                  else
                    ::Addressable::URI.unencode_component(value.tr('+', ' '))
                  end
          key = Addressable::URI.unencode_component(key)
          key = key.dup.force_encoding(Encoding::ASCII_8BIT) if key.respond_to?(:force_encoding)
          self.__send__("fill_accumulator_for_#{options[:notation]}", accumulator, key, value)
          accumulator
        end
      end

      def fill_accumulator_for_flat(accumulator, key, value)
        if accumulator[key]
          raise ArgumentError, "Key was repeated: #{key.inspect}"
        end
        accumulator[key] = value
      end

      def fill_accumulator_for_flat_array(accumulator, key, value)
        accumulator << [key, value]
      end

      def fill_accumulator_for_dot(accumulator, key, value)
        array_value = false
        subkeys = key.split(".")
        current_hash = accumulator
        subkeys[0..-2].each do |subkey|
          current_hash[subkey] = {} unless current_hash[subkey]
          current_hash = current_hash[subkey]
        end
        if array_value
          if current_hash[subkeys.last] && !current_hash[subkeys.last].is_a?(Array)

View on GitHub (pinned to b187df8827)

Solutions

  1. Use notation :flat_array - it keeps duplicates as [key, value] pairs
  2. Use :subscript (the default), which folds repeated keys into an Array under one key
  3. De-duplicate the query string upstream when the repeated keys are unintended

Example fix

// before
values = WebMock::Util::QueryMapper.query_to_values('tag=ruby&tag=testing', notation: :flat)
# => ArgumentError: Key was repeated: tag

// after
values = WebMock::Util::QueryMapper.query_to_values('tag=ruby&tag=testing', notation: :flat_array)
# => [['tag', 'ruby'], ['tag', 'testing']]
Defensive patterns

Strategy: validation

Validate before calling

def repeated_query_keys?(query)
  keys = query.to_s.split('&').map { |pair| pair.split('=').first }
  keys.size != keys.uniq.size
end

query = 'tag=ruby&tag=testing'
notation = repeated_query_keys?(query) ? :flat_array : :flat
values = WebMock::Util::QueryMapper.query_to_values(query, notation: notation)

Prevention

When it happens

Trigger: WebMock::Util::QueryMapper.query_to_values('q=1&q=2', notation: :flat); URLs with repeated parameters (tag=ruby&tag=testing, duplicated utm_source) processed through :flat during URL normalization or by custom code built on QueryMapper.

Common situations: APIs that encode arrays as repeated bare keys (ids=1&ids=2) instead of bracket syntax; analytics/marketing URLs with duplicated utm parameters; switching from :subscript (the default) to :flat without checking the data; normalizing third-party URLs you do not control.

Related errors


AI-assisted analysis of bblimke/webmock@b187df8827 (2026-08-23). Data as JSON: /api/errors/960afb469b4e6662. Report an issue: GitHub.