{"record":{"id":"0c70d3cb0ed59c27","repo":"puppetlabs/puppet","slug":"failed-to-parse-retry-after-header-retry-after","errorCode":null,"errorMessage":"Failed to parse Retry-After header '%{retry_after}' as an integer or RFC 2822 date","messagePattern":"Failed to parse Retry-After header '%(.+?)' as an integer or RFC 2822 date","errorType":"exception","errorClass":"Puppet::HTTP::ProtocolError","httpStatus":null,"severity":"error","filePath":"lib/puppet/http/retry_after_handler.rb","lineNumber":75,"sourceCode":"\n    # if retry-after is far in the future, we could end up sleeping repeatedly\n    # for 30 minutes, effectively waiting indefinitely, seems like we should wait\n    # in total for 30 minutes, in which case this upper limit needs to be enforced\n    # by the client.\n    [seconds, @max_sleep].min\n  end\n\n  private\n\n  def parse_retry_after(retry_after)\n    Integer(retry_after)\n  rescue TypeError, ArgumentError\n    begin\n      tm = DateTime.rfc2822(retry_after)\n      seconds = (tm.to_time - DateTime.now.to_time).to_i\n      [seconds, 0].max\n    rescue ArgumentError\n      raise Puppet::HTTP::ProtocolError, _(\"Failed to parse Retry-After header '%{retry_after}' as an integer or RFC 2822 date\") % { retry_after: retry_after }\n    end\n  end\nend\n","sourceCodeStart":57,"sourceCodeEnd":79,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/http/retry_after_handler.rb#L57-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Change the origin to send delta-seconds (Retry-After: 30) or an IMF-fixdate (Retry-After: Wed, 21 Aug 2026 07:28:00 GMT).","If a middlebox rewrites the header, disable or reformat the rewrite in the proxy config.","If you control neither side, terminate the retry storm upstream by fixing the 503/429 condition so Retry-After is never consulted."],"exampleFix":"# upstream middleware - before\nheaders['Retry-After'] = '2026-08-21T07:28:00Z'\n\n# after\nheaders['Retry-After'] = '30'","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  client.get(uri)\nrescue Puppet::HTTP::ProtocolError => e\n  raise unless e.message.include?('Retry-After')\n  # upstream sent a non-standard header; treat as transient backoff\n  sleep 30\n  retry\nend","preventionTips":["Emit Retry-After as delta-seconds or an RFC 2822/IMF-fixdate, never ISO 8601 or duration strings.","Disable proxy middleware that rewrites or injects Retry-After with custom formats."],"tags":["puppet","http","retry-after","headers","protocol"],"backgroundTag":"invalid-retry-after-header","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}