{"record":{"id":"f7c3a6e1710a94fc","repo":"jnunemaker/httparty","slug":"headers-must-be-a-hash","errorCode":null,"errorMessage":":headers must be a hash","messagePattern":":headers must be a hash","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/httparty/request.rb","lineNumber":417,"sourceCode":"      response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }\n\n      options[:headers] ||= {}\n      options[:headers]['Cookie'] = cookies_hash.to_cookie_string\n    end\n\n    # Uses the HTTP Content-Type header to determine the format of the\n    # response It compares the MIME type returned to the types stored in the\n    # SupportedFormats hash\n    def format_from_mimetype(mimetype)\n      if mimetype && parser.respond_to?(:format_from_mimetype)\n        parser.format_from_mimetype(mimetype)\n      end\n    end\n\n    def validate\n      raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0\n      raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)\n      raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash)\n      raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]\n      raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash)\n      raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash)\n      raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash)\n    end\n\n    def post?\n      Net::HTTP::Post == http_method\n    end\n\n    def set_basic_auth_from_uri\n      if path.userinfo\n        username, password = path.userinfo.split(':')\n        options[:basic_auth] = {username: username, password: password}\n        @credentials_sent = true\n      end\n    end\n","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/jnunemaker/httparty/blob/8f4a09e343b94de9f934f388028ca97620c9b378/lib/httparty/request.rb#L399-L435","documentation":"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).","triggerScenarios":"`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.","commonSituations":"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.","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)`."],"exampleFix":"# before\nFoo.get(url, headers: \"X-Api-Key: #{key}\")   # String -> ArgumentError\n\n# after\nFoo.get(url, headers: { 'X-Api-Key' => key })","handlingStrategy":"validation","validationCode":"headers = headers.to_hash if headers.respond_to?(:to_hash)\nraise ArgumentError, ':headers must be a hash' unless headers.respond_to?(:to_hash)\nFoo.get(url, headers: headers)","typeGuard":"hash_like = ->(v) { v.respond_to?(:to_hash) }","tryCatchPattern":"begin\n  Foo.get(url, headers: hdrs)\nrescue ArgumentError => e\n  raise unless e.message.include?('headers')\n  raise ArgumentError, \"headers helper returned #{hdrs.class}; make it return a Hash\"\nend","preventionTips":["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."],"tags":["ruby","httparty","validation","argument-error","http-headers","request-options"],"backgroundTag":"invalid-argument-type","analyzedSha":"8f4a09e343b94de9f934f388028ca97620c9b378","analyzedAt":"2026-08-21T19:30:42.003Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}