puppetlabs/puppet · error · Puppet::HTTP::ProtocolError

Failed to parse Retry-After header '%{retry_after}' as an in

Error message

Failed to parse Retry-After header '%{retry_after}' as an integer or RFC 2822 date

What it means

Puppet's Retry-After parsing accepts exactly two forms: an integer number of seconds, or an RFC 2822 date-time such as 'Wed, 21 Aug 2026 07:28:00 GMT'. It first tries Integer(), then DateTime.rfc2822; if both raise, Puppet::HTTP::ProtocolError is raised naming the offending header value. Notably ISO 8601 timestamps ('2026-08-21T07:28:00Z') are RFC 2822-incompatible and fail.

Source

Thrown at lib/puppet/http/retry_after_handler.rb:75

    # if retry-after is far in the future, we could end up sleeping repeatedly
    # for 30 minutes, effectively waiting indefinitely, seems like we should wait
    # in total for 30 minutes, in which case this upper limit needs to be enforced
    # by the client.
    [seconds, @max_sleep].min
  end

  private

  def parse_retry_after(retry_after)
    Integer(retry_after)
  rescue TypeError, ArgumentError
    begin
      tm = DateTime.rfc2822(retry_after)
      seconds = (tm.to_time - DateTime.now.to_time).to_i
      [seconds, 0].max
    rescue ArgumentError
      raise Puppet::HTTP::ProtocolError, _("Failed to parse Retry-After header '%{retry_after}' as an integer or RFC 2822 date") % { retry_after: retry_after }
    end
  end
end

View on GitHub (pinned to e227c27540)

Solutions

  1. Change the origin to send delta-seconds (Retry-After: 30) or an IMF-fixdate (Retry-After: Wed, 21 Aug 2026 07:28:00 GMT).
  2. If a middlebox rewrites the header, disable or reformat the rewrite in the proxy config.
  3. If you control neither side, terminate the retry storm upstream by fixing the 503/429 condition so Retry-After is never consulted.

Example fix

# upstream middleware - before
headers['Retry-After'] = '2026-08-21T07:28:00Z'

# after
headers['Retry-After'] = '30'
Defensive patterns

Strategy: try-catch

Try / catch

begin
  client.get(uri)
rescue Puppet::HTTP::ProtocolError => e
  raise unless e.message.include?('Retry-After')
  # upstream sent a non-standard header; treat as transient backoff
  sleep 30
  retry
end

Prevention

When it happens

Trigger: A 429/503 response carries Retry-After: '90s', '1m30s', or an ISO 8601 date instead of delta-seconds/IMF-fixdate; a proxy or WAF injecting a custom-format Retry-After.

Common situations: API gateways and Envoy-derivatives emitting non-standard values; upstream services that correctly use ISO 8601 per some other spec but not RFC 7231/2822; hand-rolled middleware adding the header as a duration string.

Understand the failure class

Related errors


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