jnunemaker/httparty · error · ArgumentError
:headers must be a hash
Error message
:headers must be a hash
What it means
Request#validate raises ArgumentError ':headers must be a hash' when the per-request options[:headers] is set but does not respond to #to_hash. This is the request-level twin of the class-level headers guard: values like a raw header string, an array of pairs, or an arbitrary object fail validation right before the request is performed (after any redirects that already consumed the limit would have raised first).
Source
Thrown at lib/httparty/request.rb:417
response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }
options[:headers] ||= {}
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
end
# Uses the HTTP Content-Type header to determine the format of the
# response It compares the MIME type returned to the types stored in the
# SupportedFormats hash
def format_from_mimetype(mimetype)
if mimetype && parser.respond_to?(:format_from_mimetype)
parser.format_from_mimetype(mimetype)
end
end
def validate
raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash)
raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash)
raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash)
raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash)
end
def post?
Net::HTTP::Post == http_method
end
def set_basic_auth_from_uri
if path.userinfo
username, password = path.userinfo.split(':')
options[:basic_auth] = {username: username, password: password}
@credentials_sent = true
end
end
View on GitHub (pinned to 8f4a09e343)
Solutions
- Pass a Hash: `Foo.get(url, headers: { 'Authorization' => "Bearer #{token}" })`.
- Coerce helper output: `headers: header_pairs.to_h`.
- Add a guard in shared request wrappers: `raise unless headers.respond_to?(:to_hash)`.
Example fix
# before
Foo.get(url, headers: "X-Api-Key: #{key}") # String -> ArgumentError
# after
Foo.get(url, headers: { 'X-Api-Key' => key }) Defensive patterns
Strategy: validation
Validate before calling
headers = headers.to_hash if headers.respond_to?(:to_hash) raise ArgumentError, ':headers must be a hash' unless headers.respond_to?(:to_hash) Foo.get(url, headers: headers)
Type guard
hash_like = ->(v) { v.respond_to?(:to_hash) } Try / catch
begin
Foo.get(url, headers: hdrs)
rescue ArgumentError => e
raise unless e.message.include?('headers')
raise ArgumentError, "headers helper returned #{hdrs.class}; make it return a Hash"
end Prevention
- Make header-builder helpers return Hash, and spec-test that.
- Never interpolate raw curl-style header strings into options.
- Keep per-request headers as { 'Name' => 'value' } literals in code review examples.
When it happens
Trigger: `Foo.get(url, headers: 'Authorization: Bearer x')`, `Foo.post(url, body: d, headers: JSON.generate(h))`, or building headers via `Array(h)` before passing them, e.g. when a helper method returns an array of ['Key','value'] pairs.
Common situations: Wrapping header construction in helpers that stringify for curl logging, copying header strings from API docs, and merging a Hash with something non-hash-like via `options[:headers] = maybe_hash` where maybe_hash is nil-replaced by a string default.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- Headers must be an object which responds to #to_hash
- :basic_auth must be a hash
- :digest_auth must be a hash
- :query must be hash if using HTTP Post
- Default params must be an object which responds to #to_hash
AI-assisted analysis of jnunemaker/httparty@8f4a09e343 (2026-08-21).
Data as JSON: /api/errors/f7c3a6e1710a94fc.
Report an issue: GitHub.