{"record":{"id":"b5387bc088d7d751","repo":"puppetlabs/puppet","slug":"post-requires-a-string-body-argument","errorCode":null,"errorMessage":"'post' requires a string 'body' argument","messagePattern":"'post' requires a string 'body' argument","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/http/client.rb","lineNumber":268,"sourceCode":"  end\n\n  # Submits a POST HTTP request to the given url\n  #\n  # @param [URI] url the location to submit the http request\n  # @param [String] body the body of the POST request\n  # @param [Hash] headers merged with the default headers defined by the client. The\n  #   `Content-Type` header is required and should correspond to the type of data passed\n  #   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","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/http/client.rb#L250-L286","documentation":"Puppet::HTTP::Client#post validates that the body argument is a String and raises ArgumentError before any connection is made. Like #put, the API performs no implicit serialization; callers render payloads themselves and must also supply a Content-Type header.","triggerScenarios":"client.post(url, nil, headers: ...); client.post(url, { 'name' => 'x' }, ...) with a Hash; passing a Puppet object (node, report) that older APIs accepted directly.","commonSituations":"Porting code from Puppet::Network::HttpPool.post or the old REST client where bodies were serialized transparently; forgetting to call .to_s/.to_json on interpolated values; posting a symbol (e.g. :undefined variable).","solutions":["Serialize first: client.post(url, JSON.generate(data), headers: { 'Content-Type' => 'application/json' }).","Guard optional payloads so nil never reaches the call (default to '').","Check any wrapper gems that forward bodies verbatim from older APIs."],"exampleFix":"# before\nclient.post(url, { cert: csr.to_pem }, headers: { 'Content-Type' => 'text/plain' })\n\n# after\nclient.post(url, csr.to_pem, headers: { 'Content-Type' => 'text/plain' })","handlingStrategy":"type-guard","validationCode":"body = JSON.generate(data) unless body.is_a?(String)\nclient.post(url, body, headers: { 'Content-Type' => 'application/json' })","typeGuard":"string_body = ->(b) { b.is_a?(String) }\nraise ArgumentError, 'body must be a String' unless string_body.call(body)","tryCatchPattern":null,"preventionTips":["Serialize before calling; the API never does it for you.","Wrap optional posts so nil becomes '' or is skipped entirely."],"tags":["puppet","http","post","request-validation","ruby"],"backgroundTag":"wrong-argument-type","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}