puppetlabs/puppet · error · ArgumentError
'post' requires a 'content-type' header
Error message
'post' requires a 'content-type' header
What it means
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.
Source
Thrown at lib/puppet/http/client.rb:276
# as the `body` argument.
# @param [Hash] params encoded and set as the url query
# @!macro request_options
#
# @yield [Puppet::HTTP::Response] if a block is given yields the response
#
# @return [Puppet::HTTP::Response] the response
#
# @api public
def post(url, body, headers: {}, params: {}, options: {}, &block)
raise ArgumentError, "'post' requires a string 'body' argument" unless body.is_a?(String)
url = encode_query(url, params)
request = Net::HTTP::Post.new(url, @default_headers.merge(headers))
request.body = body
request.content_length = body.bytesize
raise ArgumentError, "'post' requires a 'content-type' header" unless request['Content-Type']
execute_streaming(request, options: options, &block)
end
# Submits a DELETE HTTP request to the given url.
#
# @param [URI] url the location to submit the http request
# @param [Hash] headers merged with the default headers defined by the client
# @param [Hash] params encoded and set as the url query
# @!macro request_options
#
# @return [Puppet::HTTP::Response] the response
#
# @api public
def delete(url, headers: {}, params: {}, options: {})
url = encode_query(url, params)
request = Net::HTTP::Delete.new(url, @default_headers.merge(headers))View on GitHub (pinned to e227c27540)
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.
Example fix
# before
client.post(url, JSON.generate(report), headers: { 'Accept' => 'application/json' })
# after
client.post(url, JSON.generate(report),
headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }) Defensive patterns
Strategy: validation
Validate before calling
headers = { 'Content-Type' => 'application/json' }.merge(headers)
client.post(url, body, headers: headers) Type guard
has_content_type = ->(h) { h.any? { |k, _| k.is_a?(String) && k == 'Content-Type' } }
raise ArgumentError, 'Content-Type required' unless has_content_type.call(headers) Prevention
- Centralize header construction; use string keys with canonical casing.
- Prefer Puppet::HTTP::Service APIs, which set Content-Type for you.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- 'put' requires a 'content-type' header
- 'post' requires a string 'body' argument
- 'post' requires a string 'body' argument
- Location response header is missing
- Failed to parse Retry-After header '%{retry_after}' as an in
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/bb9a1e46521a6fd4.
Report an issue: GitHub.