True for retryable transient HTTP failures (timeouts, 5xx, conn resets, ...). Applies to any HTTP backend, not just gateway/proxy setups.
(exc: BaseException | None)
| 70 | |
| 71 | |
| 72 | def _is_transient_http_error(exc: BaseException | None) -> bool: |
| 73 | """True for retryable transient HTTP failures (timeouts, 5xx, conn resets, ...). |
| 74 | |
| 75 | Applies to any HTTP backend, not just gateway/proxy setups. |
| 76 | """ |
| 77 | current: BaseException | None = exc |
| 78 | while current is not None: |
| 79 | if isinstance(current, (httpx.TimeoutException, httpx.NetworkError, httpx.RemoteProtocolError)): |
| 80 | return True |
| 81 | status_code = getattr(current, "status_code", None) |
| 82 | if isinstance(status_code, int) and status_code in {408, 409, 425, 500, 502, 503, 504}: |
| 83 | return True |
| 84 | response = getattr(current, "response", None) |
| 85 | response_status = getattr(response, "status_code", None) |
| 86 | if isinstance(response_status, int) and response_status in {408, 409, 425, 500, 502, 503, 504}: |
| 87 | return True |
| 88 | text = str(current).lower() |
| 89 | if any( |
| 90 | needle in text |
| 91 | for needle in ( |
| 92 | "bad gateway", |
| 93 | "gateway timeout", |
| 94 | "server disconnected", |
| 95 | "temporary failure", |
| 96 | "temporarily unavailable", |
| 97 | "connection reset", |
| 98 | "connection aborted", |
| 99 | "timed out", |
| 100 | ) |
| 101 | ): |
| 102 | return True |
| 103 | current = current.__cause__ if isinstance(current.__cause__, BaseException) else None |
| 104 | return False |
| 105 | |
| 106 | |
| 107 | def parse_json_output(raw: str, *, action_field: str = "bash_command") -> dict[str, Any]: |