Skip to content

Expose Retry-After header on all ACME responses#264

Merged
unixcharles merged 10 commits intounixcharles:masterfrom
nckslvrmn:expose-retry-after-header-on-errors-and-resources
Mar 23, 2026
Merged

Expose Retry-After header on all ACME responses#264
unixcharles merged 10 commits intounixcharles:masterfrom
nckslvrmn:expose-retry-after-header-on-errors-and-resources

Conversation

@nckslvrmn
Copy link
Copy Markdown
Contributor

@nckslvrmn nckslvrmn commented Mar 5, 2026

Per RFC 8555 §6.6 and RFC 7231 §7.1.3, CAs can include a Retry-After header on any response — rate limits, order polling, challenge polling, 503s, etc. Previously this was only surfaced on RateLimited errors.

Changes

retry_after is now consistently an Integer (seconds) across all classes

  • RateLimited: integer seconds (unchanged, defaults to 10); HTTP-date inputs are now correctly converted to seconds-until rather than returning 0
  • All other errors and resources: integer seconds, or nil if no header was present
  • sleep e.retry_after now works safely on any Acme::Client::Error subclass

New retry_after_time field everywhere

  • Always a Time (or nil), parsed via Acme::Client::Util.parse_retry_after
  • Handles both delay-seconds ("120") and HTTP-date formats per RFC 7231 §7.1.3
  • RangeError guarded for pathologically large integer values

Exposed on more places

  • Acme::Client::Error base class and all subclasses
  • Order, Authorization, and Challenge resources
  • RenewalInfo already had retry_after; now also has retry_after_time

VCR upgraded ~> 6.0CGI.parse was removed in Ruby 4.0; cassette format and config API are unchanged.

@nckslvrmn nckslvrmn force-pushed the expose-retry-after-header-on-errors-and-resources branch from 1d9ad36 to 7674aea Compare March 5, 2026 18:54
Copy link
Copy Markdown
Owner

@unixcharles unixcharles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Let me know what you think on the Time vs Integer.

Comment thread lib/acme/client/error.rb
Comment thread lib/acme/client/error.rb Outdated
@nckslvrmn
Copy link
Copy Markdown
Contributor Author

@unixcharles I reworked the implementation a bit so it always assumes time because that's how certbot does it.

@nckslvrmn nckslvrmn requested a review from unixcharles March 10, 2026 16:43
@unixcharles
Copy link
Copy Markdown
Owner

What do you think of leaving Error#retry_after as is like in the current implementation to avoid introducing a breaking change and parsing it into a Time object exposed at Error#retry_after_time?

@nckslvrmn
Copy link
Copy Markdown
Contributor Author

What do you think of leaving Error#retry_after as is like in the current implementation to avoid introducing a breaking change and parsing it into a Time object exposed at Error#retry_after_time?

yeah I didn't think about how this changes the previous expectation of that fields type. doing it that way will definitely solve that problem. and even if we get only a time back, we can still generate both. Ill get that updated!

nckslvrmn and others added 3 commits March 13, 2026 11:49
* ARI related improvements

* Fix ARI tests: extract replaces from order response, re-record cassettes with Pebble

- Add :replaces to extract_attributes in attributes_from_order_response
  (bug: replaces was sent in requests but never parsed from responses)
- Re-record VCR cassettes against Pebble with real ARI data:
  - renewal_info_supported: real suggested window + retry-after
  - new_order_with_replaces: order response includes replaces field
  - new_order_already_replaced: real 409 alreadyReplaced error
- Update certificate_chain.pem fixture with cert from Pebble
- Remove pending() from replaces tests in order_spec and renewal_info_spec

---------

Co-authored-by: Ben Burkert <ben@benburkert.com>
Co-authored-by: Nick Silverman <nckslvrmn@gmail.com>
@nckslvrmn nckslvrmn force-pushed the expose-retry-after-header-on-errors-and-resources branch from 7a37c7d to f17b14e Compare March 13, 2026 15:50
@nckslvrmn
Copy link
Copy Markdown
Contributor Author

@unixcharles I found a legitimate bug with the retry after where when we get a date back, the field was being set to 0. so now it always parses to an int and computes from date if we get a date, and then the date field is the inverse of that. should be good to go!

@nckslvrmn
Copy link
Copy Markdown
Contributor Author

Can we get this last one merged in and a release cut? I would love to make use of the retry after header on a few of the poller jobs I am working on. Thanks in advance!

@unixcharles unixcharles merged commit f43b4cd into unixcharles:master Mar 23, 2026
6 checks passed
@nckslvrmn nckslvrmn deleted the expose-retry-after-header-on-errors-and-resources branch March 23, 2026 15:58
@unixcharles
Copy link
Copy Markdown
Owner

Released in 2.0.31

@nckslvrmn
Copy link
Copy Markdown
Contributor Author

Released in 2.0.31

thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants