src/lib/http.ts:59-95:
const retryAfter = resp.headers?.get('retry-after');
const delay = retryAfter ? Math.min(30, parseFloat(retryAfter)) * 1000
: Math.min(30000, (2 ** attempt + Math.random()) * 1000);
RFC 9110 §10.2.3 permits Retry-After in two forms:
delta-seconds — e.g. Retry-After: 120
HTTP-date — e.g. Retry-After: Wed, 21 Oct 2026 07:28:00 GMT
On the date form, parseFloat returns NaN. Math.min(30, NaN) is NaN, so delay is NaN ms and the backoff is undefined behavior — setTimeout(r, NaN) coerces to 0, meaning the client retries immediately and hammers a server that explicitly asked it to wait.
This fires precisely on 429s, where the header is most likely to be present and most likely to matter.
Fix
- Detect the two forms: if
parseFloat yields NaN, try Date.parse and compute the delta from now.
- If both fail, fall back to the exponential-backoff branch rather than propagating
NaN.
- Clamp the result to the existing 30s ceiling and guard against negative deltas (a past date).
- Unit-test both header shapes plus the malformed case.
Related
Same failure family as the missing request timeout — the retry layer degrades under exactly the adverse conditions it exists to handle.
src/lib/http.ts:59-95:RFC 9110 §10.2.3 permits
Retry-Afterin two forms:delta-seconds— e.g.Retry-After: 120HTTP-date— e.g.Retry-After: Wed, 21 Oct 2026 07:28:00 GMTOn the date form,
parseFloatreturnsNaN.Math.min(30, NaN)isNaN, sodelayisNaNms and the backoff is undefined behavior —setTimeout(r, NaN)coerces to0, meaning the client retries immediately and hammers a server that explicitly asked it to wait.This fires precisely on 429s, where the header is most likely to be present and most likely to matter.
Fix
parseFloatyieldsNaN, tryDate.parseand compute the delta from now.NaN.Related
Same failure family as the missing request timeout — the retry layer degrades under exactly the adverse conditions it exists to handle.