Skip to content

Raise Peddler errors when retries are exhausted on an error status - #274

Merged
hakanensari merged 1 commit into
mainfrom
retry-exhaustion
Aug 16, 2026
Merged

Raise Peddler errors when retries are exhausted on an error status#274
hakanensari merged 1 commit into
mainfrom
retry-exhaustion

Conversation

@lineoffligbot

Copy link
Copy Markdown
Collaborator

Problem

With retries: > 0, exhausted retries raise HTTP::OutOfRetriesError from inside the http client, bypassing Response.wrap entirely. The error class for the same Amazon failure depends on client configuration:

Peddler.orders_v0.new(region, token).get_orders(...)             # 429 → Peddler::Errors::QuotaExceeded
Peddler.orders_v0.new(region, token, retries: 3).get_orders(...) # 429 ×4 → HTTP::OutOfRetriesError

Anyone following the README (rescue Peddler::Error, promised since v5.0: "All HTTP errors now raise Peddler::Error") silently stops catching throttles the moment they turn retries on — which is exactly when throttles matter most.

Verified against a local always-503 server: retriable alone retries correctly and raises OutOfRetriesError with the final response attached; that error then escapes Peddler's wrapping.

Fix

API#request rescues HTTP::OutOfRetriesError. When it carries the last HTTP response (OutOfRetriesError#response, populated whenever the final attempt produced a status), Peddler raises the same error that response would have raised unretried, minted by the same builder:

rescue HTTP::OutOfRetriesError => e
  raise e unless e.response

  raise Error.build(e.response)
end

Ruby's implicit exception chaining preserves the original in cause (asserted in the test):

Peddler::Errors::QuotaExceeded → cause: HTTP::OutOfRetriesError → cause: last network error, if any

Exhaustion with no response at all — every attempt failed at the network level, or the final attempt did (http.rb's performer only carries the last attempt's result) — re-raises untouched. There is no HTTP error to represent; wrapping transport failures is a separate, deliberately deferred design question.

Notes

  • Technically a behavior change for anyone who discovered the leak and rescues HTTP::OutOfRetriesError around Peddler calls; filed under Fixed as it restores the documented contract, with the original error still reachable via cause.
  • Raw transport errors on unretried requests (timeouts, connection resets) are unchanged and out of scope.

When retries are enabled, HTTP::OutOfRetriesError from the http client
previously bypassed Peddler's error wrapping. The same Amazon 429 would
raise a different error class depending on the retries setting, breaking
rescue Peddler::Error blocks.

API#request now rescues HTTP::OutOfRetriesError: when it carries the
last HTTP response, Peddler raises the same error that response would
have raised unretried (Ruby's implicit exception chaining preserves the
original error in cause). Exhaustion with no response (all attempts
failed at the network level) re-raises untouched.

Includes two tests covering both cases.
@hakanensari
hakanensari merged commit 05b6367 into main Aug 16, 2026
24 checks passed
@hakanensari
hakanensari deleted the retry-exhaustion branch August 16, 2026 13:10
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