Skip to content

fix(retry): cap backoff exponent to avoid OverflowError on large attempts - #1761

Open
winklemad wants to merge 1 commit into
anthropics:mainfrom
winklemad:fix-backoff-pow-overflow
Open

fix(retry): cap backoff exponent to avoid OverflowError on large attempts#1761
winklemad wants to merge 1 commit into
anthropics:mainfrom
winklemad:fix-backoff-pow-overflow

Conversation

@winklemad

Copy link
Copy Markdown

Fixes #1760.

Problem

anthropic.lib._retry.backoff(attempt, *, cap, base=2.0) returns min(cap, base ** attempt). Because base is the float 2.0, 2.0 ** attempt overflows a C double at attempt >= 1024 and raises OverflowError before min() can clamp it, so the documented cap is never returned:

from anthropic.lib._retry import backoff
backoff(1023, cap=60.0)   # 60.0
backoff(1024, cap=60.0)   # OverflowError: (34, 'Result too large')

The only caller is the environments poller (lib/environments/_poller.py, _backoff = backoff(attempt, cap=60.0)). Its counter resets to 0 on success, so this needs ~1024 consecutive transient failures — but at that point the run-forever poll loop dies with an uncaught OverflowError instead of continuing to retry at the cap.

Fix

Cap the exponent at 1000, mirroring the guard the core client already applies in _base_client.py:

# Also cap retry count to 1000 to avoid any potential overflows with `pow`
nb_retries = min(max_retries - remaining_retries, 1000)

backoff() just missed the same guard. The returned value is unchanged for every attempt — once base ** attempt reaches cap the result is always cap, which happens far below attempt = 1000 — so this only removes the crash.

Tests

Extended the existing test_backoff (tests/lib/environments/test_poller.py) with the overflow-boundary cases (attempt=1024 and attempt=5000, both expecting the 60.0 cap). They raise OverflowError before this change and pass after; the existing cases are unchanged.

Verified locally with ruff check/ruff format clean.

…mpts

`backoff()` returned `min(cap, base ** attempt)`. Because `base` is the float
`2.0`, `2.0 ** attempt` overflows a C double at `attempt >= 1024` and raises
`OverflowError` before `min()` can clamp it, so the documented cap is never
returned -- the environments poller (the only caller) then dies with an
uncaught error instead of continuing to retry at the cap.

Cap the exponent at 1000, mirroring the guard `_base_client` already applies
("Also cap retry count to 1000 to avoid any potential overflows with `pow`").
The returned value is unchanged for every attempt (once `base ** attempt`
reaches `cap` the result is always `cap`); only the crash is removed. Extended
the existing `test_backoff` with the overflow-boundary cases.
@winklemad
winklemad requested a review from a team as a code owner July 15, 2026 14:44
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.

backoff() raises OverflowError instead of returning the cap for very large attempt counts

1 participant