From b99a52a83a503e29c5cb4aaf378b023f0d8eefae Mon Sep 17 00:00:00 2001 From: MUHAMMAD SALMAN HUSSAIN <160324527+mshsheikh@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:54:48 +0500 Subject: [PATCH] Recursively extract HTTP errors from nested ExceptionGroups This PR improves how the code finds HTTP-related errors inside nested exception groups. Right now, `_extract_http_error_from_exception()` only checks the first level of `BaseExceptionGroup.exceptions`. That works when the HTTP error is directly inside the group, but it fails when the error is wrapped inside another `ExceptionGroup`. In async code, this can happen when multiple tasks fail at the same time and the errors are grouped together more than once. With this change, the method now checks exception groups recursively. That means it keeps looking inside nested groups until it finds a matching HTTP error or confirms that none is present. This makes error detection more reliable and helps the code report the real network problem instead of missing it. This is a small but important reliability fix. It does not change the normal success path. It only improves how nested errors are handled when failures happen. ### Before ```python def _extract_http_error_from_exception(self, e: BaseException) -> Exception | None: """Extract HTTP error from exception or ExceptionGroup.""" if isinstance(e, httpx.HTTPStatusError | httpx.ConnectError | httpx.TimeoutException): return e # Check if it's an ExceptionGroup containing HTTP errors if isinstance(e, BaseExceptionGroup): for exc in e.exceptions: if isinstance( exc, httpx.HTTPStatusError | httpx.ConnectError | httpx.TimeoutException ): return exc return None ``` ### After ```python def _extract_http_error_from_exception(self, e: BaseException) -> Exception | None: """Extract HTTP error from exception or ExceptionGroup recursively.""" if isinstance(e, httpx.HTTPStatusError | httpx.ConnectError | httpx.TimeoutException): return e if isinstance(e, BaseExceptionGroup): for exc in e.exceptions: result = self._extract_http_error_from_exception(exc) if result is not None: return result return None ``` ### Why this matters * It catches HTTP errors even when they are nested inside multiple exception groups. * It improves error reporting during async cleanup and grouped task failures. * It helps the retry and user error logic work with the actual root cause. * It makes the method more robust in real async failure cases. ### Impact This change is safe and low risk. It only affects exception handling when failures occur. Normal requests and successful flows are unchanged. ### Testing idea A good test would create a nested `ExceptionGroup` that contains an `httpx.ConnectError` or `httpx.TimeoutException` several levels deep, then verify that the method still finds and returns the correct HTTP error. --- src/agents/mcp/server.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/agents/mcp/server.py b/src/agents/mcp/server.py index 7ce6bbb15b..7f152676a2 100644 --- a/src/agents/mcp/server.py +++ b/src/agents/mcp/server.py @@ -712,13 +712,12 @@ def _extract_http_error_from_exception(self, e: BaseException) -> Exception | No if isinstance(e, httpx.HTTPStatusError | httpx.ConnectError | httpx.TimeoutException): return e - # Check if it's an ExceptionGroup containing HTTP errors + # Recursively check ExceptionGroups for HTTP errors if isinstance(e, BaseExceptionGroup): for exc in e.exceptions: - if isinstance( - exc, httpx.HTTPStatusError | httpx.ConnectError | httpx.TimeoutException - ): - return exc + result = self._extract_http_error_from_exception(exc) + if result is not None: + return result return None