{"record":{"id":"e1d66b0907a94d47","repo":"puppetlabs/puppet","slug":"put-does-not-accept-options","errorCode":null,"errorMessage":"PUT does not accept options","messagePattern":"PUT does not accept options","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/indirector/facts/rest.rb","lineNumber":30,"sourceCode":"    _, facts = api.get_facts(\n      request.key,\n      environment: request.environment.to_s\n    )\n    facts\n  rescue Puppet::HTTP::ResponseError => e\n    if e.response.code == 404\n      return nil unless request.options[:fail_on_404]\n\n      _, body = parse_response(e.response)\n      msg = _(\"Find %{uri} resulted in 404 with the message: %{body}\") % { uri: elide(e.response.url.path, 100), body: body }\n      raise Puppet::Error, msg\n    else\n      raise convert_to_http_error(e.response)\n    end\n  end\n\n  def save(request)\n    raise ArgumentError, _(\"PUT does not accept options\") unless request.options.empty?\n\n    session = Puppet.lookup(:http_session)\n    api = session.route_to(:puppet)\n    api.put_facts(\n      request.key,\n      facts: request.instance,\n      environment: request.environment.to_s\n    )\n\n    # preserve existing behavior\n    nil\n  rescue Puppet::HTTP::ResponseError => e\n    # always raise even if fail_on_404 is false\n    raise convert_to_http_error(e.response)\n  end\nend\n","sourceCodeStart":12,"sourceCodeEnd":47,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/indirector/facts/rest.rb#L12-L47","documentation":"The facts REST terminus implements save as an HTTP PUT, and the wire protocol has no way to transmit indirection options — so save raises ArgumentError unless request.options is completely empty. Any key in the options hash (even :environment, as the compiler's save_facts_from_request passes) makes the PUT invalid before a connection is opened.","triggerScenarios":"Puppet::Node::Facts.indirection.save(facts, nil, environment: env, transaction_uuid: uuid) while facts_terminus = rest — exactly what the server-side compiler does when saving submitted facts; custom tooling that forwards a find-style options hash into save.","commonSituations":"Setting facts_terminus = rest on a server that also compiles catalogs (the compiler saves agent-submitted facts with options); wrappers that copy request.options between operations assuming they are transport-safe.","solutions":["Call save with an empty options hash (or the plain two-argument form) when the terminus is REST","On servers that must persist agent-submitted facts, use the default file or puppetdb store terminus for facts rather than rest","Filter options before delegating: build a fresh empty hash for save instead of reusing the request's options"],"exampleFix":"# before (ruby)\nPuppet::Node::Facts.indirection.save(facts, nil,\n  environment: env, transaction_uuid: uuid)\n# with facts_terminus = rest => ArgumentError: PUT does not accept options\n\n# after\nPuppet::Node::Facts.indirection.save(facts)  # empty options, valid PUT","handlingStrategy":"validation","validationCode":"# ruby\n# options must be empty for a REST save\nPuppet::Node::Facts.indirection.save(facts)  # or save(facts, nil, {})","typeGuard":"def rest_save_options?(terminus, options)\n  terminus.to_s != 'rest' || options.empty?\nend","tryCatchPattern":"begin\n  Puppet::Node::Facts.indirection.save(facts, nil, opts)\nrescue ArgumentError => e\n  raise unless e.message.include?('PUT does not accept')\n  Puppet::Node::Facts.indirection.save(facts)\nend","preventionTips":["Never forward find-style options into save calls","Do not set facts_terminus=rest on servers that compile catalogs","Assert options.empty? in wrappers that talk to REST termini"],"tags":["puppet-rest","facts","http-put","invalid-option","indirector"],"backgroundTag":"unsupported-request-options","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}