Repository navigation
Description
Environment
- google-genai 2.32.0, and current
main(92f72b3, released as 2.33.0) - httpx 0.28.1 and httpx2 2.7.0, Python 3.10 and 3.12
Description
client.interactions never retries transport failures such as httpx.ConnectError. Retryable status codes are retried as configured, and _translate_retry_config always sets retry_connection_errors=True, but a connection error surfaces on the first attempt. In production this showed up as APIConnectionError: [Errno 9] Bad file descriptor from client.interactions.create, with no retry.
Reproduction
No network or real API key needed:
import asyncio
import httpx
from google import genai
from google.genai import types
def count_attempts(failure, use_async):
requests = []
def handler(request):
requests.append(request)
if failure == "ConnectError":
raise httpx.ConnectError("[Errno 9] Bad file descriptor", request=request)
return httpx.Response(503, json={"error": {"code": 503, "message": "unavailable"}})
client = genai.Client(
api_key="fake-key",
http_options=types.HttpOptions(
retry_options=types.HttpRetryOptions(attempts=3, initial_delay=0.01, max_delay=0.01),
**{"async_client_args" if use_async else "client_args": {"transport": httpx.MockTransport(handler)}},
),
)
try:
if use_async:
asyncio.run(client.aio.interactions.create(model="gemini-3.5-flash", input="hi"))
else:
client.interactions.create(model="gemini-3.5-flash", input="hi")
except Exception as e:
return f"{len(requests)} attempt(s), raised {type(e).__name__}"
print(f"google-genai {genai.__version__}, httpx {httpx.__version__}")
for failure in ("503", "ConnectError"):
for use_async in (False, True):
print(f"{failure:>12} {'async' if use_async else 'sync':5}: {count_attempts(failure, use_async)}")Output on main (2.32.0 prints the same counts):
google-genai 2.33.0, httpx 0.28.1
503 sync : 4 attempt(s), raised InternalServerError
503 async: 4 attempt(s), raised InternalServerError
ConnectError sync : 1 attempt(s), raised APIConnectionError
ConnectError async: 1 attempt(s), raised APIConnectionError
The httpx2 client (GOOGLE_GENAI_HTTP_CLIENT=httpx2 with httpx2.ConnectError) also makes 1 attempt.
Expected: the ConnectError case retries like the 503 case (4 attempts), then raises APIConnectionError.
Root cause
BaseSDK.do_requestruns theafter_errorhooks inside the per-attemptdo()function (_gaos/basesdk.py).CompatErrorHook.after_error(_gaos/lib/compat_errors.py) callswrap_sdk_error, which turns thehttpx.ConnectErrorintoAPIConnectionError.retry()in_gaos/utils/retries.pythen calls_is_transport_error, which only matches httpxNetworkError/TimeoutExceptionby type or MRO name.APIConnectionErrormatches neither, so the error becomes aPermanentErrorand is raised without a retry.
test_httpx2_retries passes a raw httpx2.NetworkError to retries.retry directly, so it skips the hook and cannot catch this.
Public methods in _gaos/google_genai.py already wrap their calls in wrap_sdk_call, which applies the same translation after the retry loop, so the hook can leave errors raw. The one exception is environments.files.upload, which calls self._internal.start_upload directly, so the PR wraps that call too.
The models path had the same class of bug, fixed in #2345 (httpx) and #3186 (aiohttp).
Separately, _translate_retry_config passes HttpRetryOptions.attempts through as max_retries, so attempts=3 sends 4 requests, while the field is documented as "Maximum number of attempts, including the original request".
Activity
Venkaiahbabuneelam commentedon Oct 23, 2026
Hi @fcakyon, Thank you for bringing this to our attention. We truly appreciate you flagging this issue, we will file a bug internally.
Thanks