{"record":{"id":"bb9a1e46521a6fd4","repo":"puppetlabs/puppet","slug":"post-requires-a-content-type-header","errorCode":null,"errorMessage":"'post' requires a 'content-type' header","messagePattern":"'post' requires a 'content-type' header","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/http/client.rb","lineNumber":276,"sourceCode":"  #   as the `body` argument.\n  # @param [Hash] params encoded and set as the url query\n  # @!macro request_options\n  #\n  # @yield [Puppet::HTTP::Response] if a block is given yields the response\n  #\n  # @return [Puppet::HTTP::Response] the response\n  #\n  # @api public\n  def post(url, body, headers: {}, params: {}, options: {}, &block)\n    raise ArgumentError, \"'post' requires a string 'body' argument\" unless body.is_a?(String)\n\n    url = encode_query(url, params)\n\n    request = Net::HTTP::Post.new(url, @default_headers.merge(headers))\n    request.body = body\n    request.content_length = body.bytesize\n\n    raise ArgumentError, \"'post' requires a 'content-type' header\" unless request['Content-Type']\n\n    execute_streaming(request, options: options, &block)\n  end\n\n  # Submits a DELETE HTTP request to the given url.\n  #\n  # @param [URI] url the location to submit the http request\n  # @param [Hash] headers merged with the default headers defined by the client\n  # @param [Hash] params encoded and set as the url query\n  # @!macro request_options\n  #\n  # @return [Puppet::HTTP::Response] the response\n  #\n  # @api public\n  def delete(url, headers: {}, params: {}, options: {})\n    url = encode_query(url, params)\n\n    request = Net::HTTP::Delete.new(url, @default_headers.merge(headers))","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/http/client.rb#L258-L294","documentation":"Puppet::HTTP::Client#post requires a Content-Type header: default client headers (User-Agent, Accept, etc.) do not include one, so a request whose merged headers lack it raises ArgumentError just before send. The check reads request['Content-Type'], which is case-sensitive.","triggerScenarios":"client.post(url, body) with no headers argument; headers: { 'content-type' => 'application/json' } in lowercase; headers supplied with symbol keys :Content_Type that never reach the Net::HTTP request.","commonSituations":"Submitting reports or CA CSRs with hand-rolled calls where the old API defaulted the header; header hashes built from symbolized config keys; casing mismatches after refactoring shared header constants.","solutions":["Pass 'Content-Type' (exact casing, string key) with a value matching the body, e.g. 'application/json'.","Use the service layer (Puppet::HTTP::Service::Report, ::Ca) which negotiates Content-Type itself.","Centralize header construction in one helper so casing mistakes cannot diverge per call site."],"exampleFix":"# before\nclient.post(url, JSON.generate(report), headers: { 'Accept' => 'application/json' })\n\n# after\nclient.post(url, JSON.generate(report),\n            headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' })","handlingStrategy":"validation","validationCode":"headers = { 'Content-Type' => 'application/json' }.merge(headers)\nclient.post(url, body, headers: headers)","typeGuard":"has_content_type = ->(h) { h.any? { |k, _| k.is_a?(String) && k == 'Content-Type' } }\nraise ArgumentError, 'Content-Type required' unless has_content_type.call(headers)","tryCatchPattern":null,"preventionTips":["Centralize header construction; use string keys with canonical casing.","Prefer Puppet::HTTP::Service APIs, which set Content-Type for you."],"tags":["puppet","http","post","headers","content-type"],"backgroundTag":"missing-content-type-header","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}