From 51c1ba6976205671eea8da737580229ef155e2c9 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:27:41 -0700 Subject: [PATCH] fix(agent): apply pool-level keepalive to the process_bootstrap sibling builder The salvaged #54550 converted AIAgent._build_keepalive_http_client but the near-identical build_keepalive_http_client in agent/process_bootstrap.py (used by auxiliary clients: compression, vision, web_extract, titles) kept the socket_options transport and the api.githubcopilot.com bypass. Same conversion: httpx.Limits(keepalive_expiry=20) + pool timeouts, verify forwarded on client and no-proxy mounts, copilot hardcode removed. --- agent/process_bootstrap.py | 51 ++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/agent/process_bootstrap.py b/agent/process_bootstrap.py index 9790dbca9cf..89b6278a8c2 100644 --- a/agent/process_bootstrap.py +++ b/agent/process_bootstrap.py @@ -151,42 +151,51 @@ def build_keepalive_http_client( """Build an httpx client for OpenAI SDK calls with env-only proxy policy. Uses explicit ``HTTPS_PROXY`` / ``NO_PROXY`` env vars via - ``_get_proxy_for_base_url``. A custom transport disables httpx's default - ``trust_env`` path, so macOS system proxy settings from + ``_get_proxy_for_base_url``. Plain no-proxy mounts disable httpx's default + ``trust_env`` proxy path, so macOS system proxy settings from ``urllib.request.getproxies()`` (which omit the ExceptionsList) are not applied. Mirrors ``AIAgent._build_keepalive_http_client``. + Connection lifecycle is managed at the HTTP pool layer + (``keepalive_expiry=20.0`` reaps idle connections before reverse proxies' + typical 30-60 s timeouts) instead of the former custom + ``socket_options`` transport, which broke streaming behind reverse + proxies (#54049, #12952) and stalled TLS handshakes by stripping + ``TCP_NODELAY``. + ``verify`` is forwarded to httpx so auxiliary-client calls (compression, vision, web_extract, title generation, etc.) honor the same per-provider ``ssl_ca_cert`` / ``ssl_verify`` and ``HERMES_CA_BUNDLE`` settings the main - client uses. It is passed on the ``HTTPTransport`` (which owns the SSL - context when a custom transport is supplied) and, for the copilot branch - that has no custom transport, on the client itself. + client uses. It is passed on the client AND on the plain no-proxy mounts + (a mounted transport owns the SSL context for its scheme). """ try: import httpx - import socket - - if "api.githubcopilot.com" in str(base_url or "").lower(): - client_cls = httpx.AsyncClient if async_mode else httpx.Client - return client_cls(verify=verify) - - sock_opts = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)] - if hasattr(socket, "TCP_KEEPIDLE"): - sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 30)) - sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)) - sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)) - elif hasattr(socket, "TCP_KEEPALIVE"): - sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, 30)) proxy = _get_proxy_for_base_url(base_url) + + limits = httpx.Limits( + max_keepalive_connections=20, + max_connections=100, + keepalive_expiry=20.0, + ) + # Generous read=None for SSE streaming endpoints. + timeout = httpx.Timeout(connect=15.0, read=None, write=15.0, pool=10.0) + transport_cls = httpx.AsyncHTTPTransport if async_mode else httpx.HTTPTransport client_cls = httpx.AsyncClient if async_mode else httpx.Client - # verify lives on the transport: httpx ignores the client-level - # ``verify`` when a custom ``transport=`` is supplied. + mounts = {} + if proxy is None: + mounts = { + "http://": transport_cls(verify=verify), + "https://": transport_cls(verify=verify), + } return client_cls( - transport=transport_cls(socket_options=sock_opts, verify=verify), + limits=limits, + timeout=timeout, proxy=proxy, + mounts=mounts or None, + verify=verify, ) except Exception: return None