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
- 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
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
- 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
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
- Find %{uri} resulted in 404 with the message: %{body}
- Facts but no fact format provided for %{request}
- Find %{uri} resulted in 404 with the message: %{body}
- Catalog environment didn't stabilize after %{tries} fetches,
- Could not retrieve local facts: %{detail}
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/e1d66b0907a94d47.
Report an issue: GitHub.