puppetlabs/puppet · error · ArgumentError

'put' requires a 'content-type' header

Error message

'put' requires a 'content-type' header

What it means

Puppet::HTTP::Client#put requires a Content-Type header on every request: the client's default headers do not include one, so if the caller's headers hash doesn't supply it (with that exact case), an ArgumentError is raised after the body is set but before the request is sent.

Source

Thrown at lib/puppet/http/client.rb:247

  # @param [Hash] headers merged with the default headers defined by the client. The
  #   `Content-Type` header is required and should correspond to the type of data passed
  #   as the `body` argument.
  # @param [Hash] params encoded and set as the url query
  # @!macro request_options
  #
  # @return [Puppet::HTTP::Response] the response
  #
  # @api public
  def put(url, body, headers: {}, params: {}, options: {})
    raise ArgumentError, "'put' requires a string 'body' argument" unless body.is_a?(String)

    url = encode_query(url, params)

    request = Net::HTTP::Put.new(url, @default_headers.merge(headers))
    request.body = body
    request.content_length = body.bytesize

    raise ArgumentError, "'put' requires a 'content-type' header" unless request['Content-Type']

    execute_streaming(request, options: options)
  end

  # Submits a POST HTTP request to the given url
  #
  # @param [URI] url the location to submit the http request
  # @param [String] body the body of the POST request
  # @param [Hash] headers merged with the default headers defined by the client. The
  #   `Content-Type` header is required and should correspond to the type of data passed
  #   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
  #

View on GitHub (pinned to e227c27540)

Solutions

  1. Add the header with exact casing: headers: { 'Content-Type' => 'application/json' }.
  2. Match the type to the body's serialization (application/json for JSON, text/plain for plain text, application/x-www-form-urlencoded for form bodies).
  3. Prefer the high-level Puppet::HTTP::Service APIs, which set Content-Type for you.

Example fix

# before
client.put(url, JSON.generate(data), headers: {})

# after
client.put(url, JSON.generate(data), headers: { 'Content-Type' => 'application/json' })
Defensive patterns

Strategy: validation

Validate before calling

headers = { 'Content-Type' => 'application/json' }.merge(headers)
client.put(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

When it happens

Trigger: client.put(url, 'text') with headers: {} ; passing the header lowercase as 'content-type' — Net::HTTP header lookup in the guard is case-sensitive, so request['Content-Type'] stays nil even though Net::HTTP would send it.

Common situations: Sending JSON without 'application/json' because the old HttpPool API added it; header-name casing copied from a different HTTP library; proxy code forwarding only some headers.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/539fd5b377c22ff6. Report an issue: GitHub.