Skip to content

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

Description

@winklemad

Summary

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 instead of the documented cap (the docstring says "capped at cap") the call raises.

Reproduction

from anthropic.lib._retry import backoff

print(backoff(1023, cap=60.0))   # 60.0
print(backoff(1024, cap=60.0))   # OverflowError: (34, 'Result too large')

Where it bites

The only caller is the environments poller — lib/environments/_poller.py uses _backoff(attempt) = backoff(attempt, cap=60.0) in its retry loop. poll/ack reset the attempt counter to 0 on any success, so triggering this needs ~1024 consecutive transient failures. At that point the run-forever poller dies with an uncaught OverflowError and won't recover once the outage clears, instead of continuing to retry at the 60s cap.

Note

The core client already guards this exact case — 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)
...
sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)

The extracted backoff() helper simply missed the same exponent guard. Happy to send a PR mirroring it (min(cap, base ** min(attempt, 1000))) — the returned value is unchanged for every attempt (the result is already cap long before the overflow point), it only removes the crash.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions