> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# The Retry That Ignored the Retry-After Header
- URL: https://debugly.dev/retry-ignored-retry-after-header/
- Published: 2026-10-09T18:13:00.000Z
- Updated: 2026-10-17T05:55:28.000Z
- Description: The client retried on its own schedule instead of the server's, so the backoff was shorter than the throttle window, and the account stayed limited long…
- Author: Rohit Bhadani
- Tags: Error Autopsy, HTTP, Reliability

Start at the failed and you miss it. Start at the stuck and you miss how it arrived. The bug lived in the hand-off.

The retry that negotiates with itself has a single weak property, and here it is: many rate limiters extend or reset the penalty when a client keeps retrying inside the window, so the client's own persistence is what keeps it locked out.

The case was a client calling a partner API; the mechanics generalise to any limiter that publishes a Retry-After value.

## Why the header was ignored

The client library was the generic, and the generic was the retry with the exponential backoff, and the exponential was the two and the four and the eight, and the eight was the client's schedule, and the schedule was the not the server's, and the server's was the header, and the header was the sixty, and the sixty was the ignored, and the ignored was the retry inside the window.

The library's default was the convenience, and the convenience was the no configuration, and the no configuration was the generic backoff, and the generic backoff was the reasonable for the transient error, and the transient was the not the throttle, and the not throttle was the difference, and the difference was the header, and the header was the server's instruction, and the instruction was the fix's target.

The header is the negotiation, and the negotiation is the server's knowledge, and the knowledge is the window's length, and the length is the not knowable by the client, and the not knowable is the reason the header exists, and the exists is the fix, and the fix is the read, and the read is the one line.

## Why the lockout extended

The retry was the inside the window, and the inside was the new request, and the new request was the limiter's counter, and the counter was the incremented, and the incremented was the penalty's extension, and the extension was the longer window, and the longer window was the further lockout, and the lockout was the client's retry, and the retry was the extension, and the loop was the self-sustaining.

The traffic had stopped, and the stopped was the user's action, and the action was the not the client's, and the client's was the background retry, and the background retry was the queue, and the queue was the persistent, and the persistent was the lockout's continuation, and the continuation was the incident's duration, and the duration was the hour, and the hour was the header's absence.

This is the same shape as [the agent retry loop that rate limited a partner API](https://debugly.dev/agent-retry-loop-rate-limited/), and the shared property is the one worth fixing.

## The fix, in order

**Read the Retry-After header and wait exactly that long.** The header was the parsed, and the parsed was the wait, and the wait was the server's schedule, and the schedule was the fix, and the fix was the branch, and the branch was the discipline, because the header is the only party that knows the window.

**Handled both the seconds and the date forms.** The header is the integer or the HTTP date, and the two are the forms, and the forms are the parse, and the parse is the fix, and the fix is the branch, and the branch is the discipline, because the date form breaks the integer parse and the broken parse is the fallback to the wrong backoff.

**Stopped retrying the 429 beyond a small budget.** The budget was the two attempts, and the attempts were the bound, and the bound was the lockout's end, and the end was the fix, and the fix was the cap, and the cap was the discipline, because the unlimited retry on the throttle is the self-extension.

**Surfaced the throttle to the caller rather than hiding it.** The error was the raised, and the raised was the caller's decision, and the decision was the queue or the drop, and the two were the explicit, and the explicit was the fix, and the fix was the error type, and the type was the discipline, because the swallowed 429 is the invisible backpressure.

**Logged the header value alongside the failure.** The logged was the diagnosis, and the diagnosis was the comparison, and the comparison was the client's wait against the server's, and the against was the mismatch's proof, and the proof was the fix's evidence, and the evidence was the discipline.

## What I now do

A 429 response carries the server's own instruction in Retry-After, and a client that retries on its own schedule ignores it, often extending the lockout. Parse both the seconds and the date forms, wait exactly the stated duration, cap the retry budget, and surface the throttle to the caller.

The client stayed rate limited for an hour after the traffic had stopped, because it retried every few seconds on its own exponential schedule while the server's Retry-After header asked for sixty, and each early retry extended the penalty. Name that one property in review and this class of bug gets hard to ship.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)