Skip to content

httpx.TimeoutException is not retried despite HttpRetryOptions being configured #2337

Closed

Description

@hadamove-rapidsos

Is this a client library issue or a product issue?

This is a client library issue (google-genai). The SDK's retry predicate only matches errors.APIError subclasses, meaning lower-level HTTP transport exceptions (httpx.TimeoutException and its subclasses) are silently dropped and never retried - even when the user has explicitly configured HttpRetryOptions.


Environment details

  • Programming language: Python
  • OS: macOS (darwin 24.6.0), also reproducible on any OS
  • Language runtime version: Python 3.12.12
  • Package version: google-genai==1.73.1

Steps to reproduce

The following self-contained script has no external dependencies, no credentials, and makes no network calls

import contextlib
import httpx
import tenacity
from google.genai._api_client import retry_args
from google.genai.errors import ServerError
from google.genai.types import HttpRetryOptions

_ATTEMPTS = 3
_OPTIONS = HttpRetryOptions(
    attempts=_ATTEMPTS,
    http_status_codes=[408, 429, 500, 502, 503, 504],
    initial_delay=0.0,
    max_delay=0.0,
)


async def _run(fn) -> int:
    call_count = 0

    async def wrapped():
        nonlocal call_count
        call_count += 1
        return await fn()

    kwargs = retry_args(_OPTIONS)
    async_retry = tenacity.AsyncRetrying(**kwargs)
    with contextlib.suppress(httpx.TimeoutException, Exception):
        await async_retry(wrapped)
    return call_count


# ✅ Works as expected: 500 ServerError is retried 3 times.
async def raises_500():
    raise ServerError(500, {"error": {"code": 500, "message": "oops", "status": "INTERNAL"}})

# ❌Bug: httpx.ReadTimeout is never retried regardless of `attempts`.
async def raises_timeout():
    raise httpx.ReadTimeout("simulated network stall")

Expected behavior:
raises_timeout() should be retried _ATTEMPTS (3) times. Google's own Vertex AI retry strategy docs explicitly list "Socket timeouts and TCP disconnects" as retryable errors: https://cloud.google.com/vertex-ai/generative-ai/docs/retry-strategy

Actual behavior:
raises_timeout() is called exactly once and the exception propagates immediately, completely bypassing the configured HttpRetryOptions.

Root cause:
In _api_client.py, the tenacity retry predicate is:

retry = tenacity.retry_if_exception(
    lambda e: isinstance(e, errors.APIError) and e.code in retriable_codes,
)

httpx.TimeoutException (and its subclasses ReadTimeout, ConnectTimeout, etc.) do not inherit from errors.APIError, so the predicate always returns False and tenacity stops after the first attempt.

Why this matters in production:
LLM inference calls can take 30–60+ seconds. In that window, cloud load balancers, NAT gateways, and firewalls may silently drop idle TCP connections, causing the httpx client to raise ReadTimeout mid-request. Without retry coverage for these exceptions, callers experience hard failures from transient infrastructure noise - the exact scenario HttpRetryOptions is supposed to protect against.

Suggested fix:
Extend the retry predicate in retry_args() to also cover httpx.TimeoutException and httpx.ConnectError:

retry = tenacity.retry_if_exception(
    lambda e: (isinstance(e, errors.APIError) and e.code in retriable_codes)
              or isinstance(e, (httpx.TimeoutException, httpx.ConnectError))
)

Activity

  1. added
    type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.
    priority: p2Moderately-important priority. Fix may not be included in next release.
    on Apr 24, 2026
  2. added a commit that references this issue on Apr 27, 2026
    d113ed4
  3. added a commit that references this issue on Apr 27, 2026
    0598bab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

priority: p2Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions