From 862f2896885b1c7bf62db1b40478c68bfb9361ce Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 23 Mar 2026 22:18:29 +0800 Subject: [PATCH 1/2] fix: handle math.inf as max_retries without crashing 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. --- src/anthropic/_base_client.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/anthropic/_base_client.py b/src/anthropic/_base_client.py index 963cd1b9..c1b31078 100644 --- a/src/anthropic/_base_client.py +++ b/src/anthropic/_base_client.py @@ -368,7 +368,7 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _client: _HttpxClientT _version: str _base_url: URL - max_retries: int + max_retries: int | float timeout: Union[float, Timeout, None] _strict_response_validation: bool _idempotency_header: str | None @@ -1047,7 +1047,11 @@ def request( max_retries = input_options.get_max_retries(self.max_retries) retries_taken = 0 - for retries_taken in range(max_retries + 1): + # 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): options = model_copy(input_options) options = self._prepare_options(options) @@ -1687,7 +1691,8 @@ async def request( max_retries = input_options.get_max_retries(self.max_retries) retries_taken = 0 - for retries_taken in range(max_retries + 1): + range_limit = max_retries + 1 if max_retries < sys.maxsize else sys.maxsize + for retries_taken in range(range_limit): options = model_copy(input_options) options = await self._prepare_options(options) From 3a0b8a7c4bc7cf1989384072018e8a2be62ba36a Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 23 Mar 2026 23:39:11 +0800 Subject: [PATCH 2/2] fix: handle finite float max_retries and fix type annotations - 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) --- src/anthropic/_base_client.py | 19 ++++++++++++++----- src/anthropic/_models.py | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/anthropic/_base_client.py b/src/anthropic/_base_client.py index c1b31078..bc34419a 100644 --- a/src/anthropic/_base_client.py +++ b/src/anthropic/_base_client.py @@ -380,7 +380,7 @@ def __init__( version: str, base_url: str | URL, _strict_response_validation: bool, - max_retries: int = DEFAULT_MAX_RETRIES, + max_retries: int | float = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -913,7 +913,7 @@ def __init__( *, version: str, base_url: str | URL, - max_retries: int = DEFAULT_MAX_RETRIES, + max_retries: int | float = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, @@ -1050,7 +1050,11 @@ def request( # 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 + # Also wrap with int() to handle finite floats like 2.0 (range(3.0) raises TypeError). + if max_retries == math.inf or max_retries >= sys.maxsize: + range_limit = sys.maxsize + else: + range_limit = int(max_retries) + 1 for retries_taken in range(range_limit): options = model_copy(input_options) options = self._prepare_options(options) @@ -1556,7 +1560,7 @@ def __init__( version: str, base_url: str | URL, _strict_response_validation: bool, - max_retries: int = DEFAULT_MAX_RETRIES, + max_retries: int | float = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, @@ -1691,7 +1695,12 @@ async def request( max_retries = input_options.get_max_retries(self.max_retries) retries_taken = 0 - range_limit = max_retries + 1 if max_retries < sys.maxsize else sys.maxsize + # Cap max_retries for range() — math.inf is a float and cannot be passed + # to range(). Also wrap with int() to handle finite floats like 2.0 (range(3.0) raises TypeError). + if max_retries == math.inf or max_retries >= sys.maxsize: + range_limit = sys.maxsize + else: + range_limit = int(max_retries) + 1 for retries_taken in range(range_limit): options = model_copy(input_options) options = await self._prepare_options(options) diff --git a/src/anthropic/_models.py b/src/anthropic/_models.py index 2d979355..be7cd3a3 100644 --- a/src/anthropic/_models.py +++ b/src/anthropic/_models.py @@ -878,7 +878,7 @@ class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] else: model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - def get_max_retries(self, max_retries: int) -> int: + def get_max_retries(self, max_retries: int | float) -> int | float: if isinstance(self.max_retries, NotGiven): return max_retries return self.max_retries