Skip to content

fix: handle math.inf as max_retries without TypeError crash#1282

Open
gn00295120 wants to merge 2 commits intoanthropics:mainfrom
gn00295120:fix/max-retries-math-inf-support
Open

fix: handle math.inf as max_retries without TypeError crash#1282
gn00295120 wants to merge 2 commits intoanthropics:mainfrom
gn00295120:fix/max-retries-math-inf-support

Conversation

@gn00295120
Copy link
Copy Markdown

Summary

max_retries=math.inf is recommended in the __init__ error message for unlimited retries, but causes a TypeError crash on the first API call.

Problem

# The error message explicitly recommends math.inf:
"max_retries cannot be None. If you want unlimited retries, pass `math.inf`..."

# But range() requires an integer:
for retries_taken in range(max_retries + 1):  # TypeError: 'float' object cannot be interpreted as an integer

Before fix:

import math
from anthropic import Anthropic

client = Anthropic(max_retries=math.inf)
client.messages.create(...)
# TypeError: 'float' object cannot be interpreted as an integer

After fix:

client = Anthropic(max_retries=math.inf)
client.messages.create(...)  # works — retries up to sys.maxsize times

Changes

  • Cap range() limit at sys.maxsize when max_retries exceeds it (handles math.inf and very large floats)
  • Fix applied to both sync (_retry_request) and async (_retry_request) paths
  • Update max_retries type annotation to int | float on BaseClient

Test plan

  • math.inf: range() no longer raises TypeError
  • max_retries=2: range_limit = 3 (unchanged behavior)
  • max_retries=0: range_limit = 1 (unchanged behavior)
  • 151 existing client tests pass, 0 failures

The error message in __init__ explicitly recommends math.inf for
unlimited retries, but range(math.inf + 1) raises TypeError because
range() requires an integer. Cap the range limit at sys.maxsize when
max_retries is infinity or exceeds sys.maxsize.

Both sync (_retry_request) and async (_retry_request) paths are fixed.
Copilot AI review requested due to automatic review settings March 23, 2026 14:18
@gn00295120 gn00295120 requested a review from a team as a code owner March 23, 2026 14:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a runtime crash when users follow the client’s own guidance to set max_retries=math.inf for “unlimited retries”, by preventing range() from receiving an infinite value in the request retry loop(s).

Changes:

  • Broaden BaseClient.max_retries annotation to int | float.
  • Add a capped range_limit for the sync request retry loop to avoid TypeError when max_retries is math.inf.
  • Apply the same retry-loop change to the async request path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1050 to +1054
# Cap max_retries for range() — math.inf is a float and cannot be passed
# to range(). The error message in __init__ advertises math.inf as valid,
# so we must handle it here. Use sys.maxsize as the effective upper bound.
range_limit = max_retries + 1 if max_retries < sys.maxsize else sys.maxsize
for retries_taken in range(range_limit):
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

range_limit can still become a float when max_retries is a finite float < sys.maxsize (e.g. 2.0 or 3.5), which will still raise TypeError in range(range_limit). Since __init__ currently advertises passing “a very high number” and this PR expands the type to int | float, consider normalizing here (or earlier) to an int (e.g. accept only integer-valued floats via .is_integer(), handle math.inf explicitly, and reject nan/non-integral values with a clear error).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — now explicitly checks for math.inf and >= sys.maxsize, then uses int(max_retries) + 1 for finite values. Applied to both sync and async loops.

Comment on lines +1052 to +1054
# so we must handle it here. Use sys.maxsize as the effective upper bound.
range_limit = max_retries + 1 if max_retries < sys.maxsize else sys.maxsize
for retries_taken in range(range_limit):
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capping range_limit to sys.maxsize changes the semantics for max_retries >= sys.maxsize: the loop will run sys.maxsize times, which corresponds to sys.maxsize - 1 retries (since the first iteration is the initial attempt). If the intent is “retries up to sys.maxsize times” (per PR description), the cap likely needs to be applied to max_retries and then use effective_max_retries + 1 for the range (i.e. allow sys.maxsize + 1 iterations when max_retries is infinite/very large).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Practically irrelevant — whether it's sys.maxsize or sys.maxsize - 1 retries, both are effectively infinite. Adding + 1 could actually overflow.

Comment on lines +1694 to +1695
range_limit = max_retries + 1 if max_retries < sys.maxsize else sys.maxsize
for retries_taken in range(range_limit):
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async retry loop uses the same range_limit computation, so it has the same failure mode when max_retries is a finite float (< sys.maxsize) and the same off-by-one semantics when capping at sys.maxsize. Consider sharing a small helper to compute an int iteration limit to keep sync/async behavior identical and avoid drifting fixes.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — both sync and async loops now use the same corrected logic.

Comment on lines 368 to 372
_client: _HttpxClientT
_version: str
_base_url: URL
max_retries: int
max_retries: int | float
timeout: Union[float, Timeout, None]
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the attribute annotation to max_retries: int | float helps communicate support for math.inf, but the constructor parameter is still annotated as int and FinalRequestOptions.get_max_retries() is typed to take/return int (see src/anthropic/_models.py). This inconsistency will surface for type-checkers and makes it unclear which layer is responsible for validating/coercing floats; consider updating the public __init__ signature and/or ensuring get_max_retries() continues to return an int by normalizing self.max_retries at assignment time.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — updated __init__ parameter types in BaseClient, SyncAPIClient, and AsyncAPIClient to int | float. Also updated FinalRequestOptions.get_max_retries() return type.

- Fix TypeError when max_retries is a finite float (e.g. 2.0): range(3.0)
  raises TypeError, so wrap with int() before passing to range().
- Use explicit inf/maxsize check instead of a single comparison that
  could overflow for very large floats just under sys.maxsize.
- Update all three __init__ max_retries parameters from `int` to
  `int | float` to match the class attribute annotation.
- Update FinalRequestOptions.get_max_retries() signature to accept and
  return `int | float` consistently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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