puppetlabs/puppet · error · ArgumentError

PUT does not accept options

Error message

PUT does not accept options

What it means

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.

Source

Thrown at lib/puppet/indirector/facts/rest.rb:30

    _, facts = api.get_facts(
      request.key,
      environment: request.environment.to_s
    )
    facts
  rescue Puppet::HTTP::ResponseError => e
    if e.response.code == 404
      return nil unless request.options[:fail_on_404]

      _, body = parse_response(e.response)
      msg = _("Find %{uri} resulted in 404 with the message: %{body}") % { uri: elide(e.response.url.path, 100), body: body }
      raise Puppet::Error, msg
    else
      raise convert_to_http_error(e.response)
    end
  end

  def save(request)
    raise ArgumentError, _("PUT does not accept options") unless request.options.empty?

    session = Puppet.lookup(:http_session)
    api = session.route_to(:puppet)
    api.put_facts(
      request.key,
      facts: request.instance,
      environment: request.environment.to_s
    )

    # preserve existing behavior
    nil
  rescue Puppet::HTTP::ResponseError => e
    # always raise even if fail_on_404 is false
    raise convert_to_http_error(e.response)
  end
end

View on GitHub (pinned to e227c27540)

Solutions

  1. Call save with an empty options hash (or the plain two-argument form) when the terminus is REST
  2. On servers that must persist agent-submitted facts, use the default file or puppetdb store terminus for facts rather than rest
  3. Filter options before delegating: build a fresh empty hash for save instead of reusing the request's options

Example fix

# before (ruby)
Puppet::Node::Facts.indirection.save(facts, nil,
  environment: env, transaction_uuid: uuid)
# with facts_terminus = rest => ArgumentError: PUT does not accept options

# after
Puppet::Node::Facts.indirection.save(facts)  # empty options, valid PUT
Defensive patterns

Strategy: validation

Validate before calling

# ruby
# options must be empty for a REST save
Puppet::Node::Facts.indirection.save(facts)  # or save(facts, nil, {})

Type guard

def rest_save_options?(terminus, options)
  terminus.to_s != 'rest' || options.empty?
end

Try / catch

begin
  Puppet::Node::Facts.indirection.save(facts, nil, opts)
rescue ArgumentError => e
  raise unless e.message.include?('PUT does not accept')
  Puppet::Node::Facts.indirection.save(facts)
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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