NousResearch/hermes-agent

Telegram CLOSE_WAIT fd leak keepalive fix

Cluster 58790. Data as of

Nothing open on GitHub. Fix merged in #58982 on 5 Jul 2026. Check it covers your case.

The short answer

0open issues
0open PRs
4items grouped
Known?
Yes. 4 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #58982. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #58982. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and PRs address a file-descriptor leak caused by CLOSE_WAIT connections in the Telegram transport, specifically where keepalive limits were not being applied to the fallback or general-pool transport. The work involves identifying the gap and wiring keepalive settings into the affected transports.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #58982 fix(telegram): forward keepalive limits into fallback transport (stop CLOSE_WAIT fd leak)

    merged to main

    files: plugins/platforms/telegram/adapter.py, tests/gateway/test_telegram_network.py

    Show the diff
    • plugins/platforms/telegram/adapter.py
      @@ -3025,8 +3025,11 @@ def _env_float(name: str, default: float) -> float:
                   def _with_limits(httpx_kwargs: Optional[dict] = None) -> dict:
                       """Merge tuned keepalive limits into httpx client kwargs.
       
      -                A caller-supplied ``limits`` (none today) is left untouched;
      -                otherwise the CLOSE_WAIT-safe limits are injected.
      +                Used by the proxy and direct-DNS branches, where httpx honours
      +                the client-level ``limits`` kwarg. A caller-supplied ``limits``
      +                is left untouched; otherwise the CLOSE_WAIT-safe limits are
      +                injected. The fallback-IP branch does NOT use this helper — see
      +                the ``_transport_kwargs`` note below for why.
                       """
                       kwargs = dict(httpx_kwargs or {})
                       if _pool_limits is not None and "limits" not in kwargs:
      @@ -3054,17 +3057,31 @@ def _with_limits(httpx_kwargs: Optional[dict] = None) -> dict:
                       )
                       # Keep request/update pools separate to reduce contention during
                       # polling reconnect + bot API bootstrap/delete_webhook calls.
      +                # httpx ignores the client-level `limits` kwarg when a custom
      +                # `transport` is supplied (#58790).  Unlike the proxy/direct
      +                # branches (which inject limits at the client level via
      +                # `_with_limits`), this branch MUST pass the tuned limits
      +                # directly into TelegramFallbackTransport so its inner
      +                # AsyncHTTPTransport instances honour keepalive_expiry — do not
      +                # route this through `_with_limits`, httpx would discard it.
      +                _transport_kwargs: dict = {}
      +                if _pool_limits is not None:
      +                    _transport_kwargs["limits"] = _pool_limits
                       request = HTTPXRequest(
                           **request_kwargs,
      -                    httpx_kwargs=_with_limits(
      -                        {"transport": TelegramFallbackTransport(fallback_ips)}
      -                    ),
      +                    httpx_kwargs={
      +                        "transport": TelegramFallbackTransport(
      +                            fallback_ips, **_transport_kwargs
      +                        )
      +                    },
                       )
                       get_updates_request = HTTPXRequest(
                           **request_kwargs,
      -                    httpx_kwargs=_with_limits(
      -                        {"transport": TelegramFallbackTransport(fallback_ips)}
      -                    ),
      +                    httpx_kwargs={
      +                        "transport": TelegramFallbackTransport(
      +                            fallback_ips, **_transport_kwargs
      +                        )
      +                    },
                       )
                   elif proxy_url:
                       logger.info("[%s] Proxy detected; passing explicitly to HTTPXRequest: %s", self.name, proxy_url)
    • tests/gateway/test_telegram_network.py
      @@ -354,6 +354,37 @@ def factory(**kwargs):
               assert len(seen_kwargs) == 2
               assert all("proxy" not in kwargs for kwargs in seen_kwargs)
       
      +    def test_forwards_limits_to_inner_transports(self, monkeypatch):
      +        """Verify that caller-supplied limits reach the inner
      +        AsyncHTTPTransport instances (#58790).  httpx ignores the
      +        client-level limits kwarg when a custom transport is
      +        supplied, so the limits must be forwarded via transport_kwargs.
      +        """
      +        seen_kwargs = []
      +
      +        def factory(**kwargs):
      +            seen_kwargs.append(kwargs.copy())
      +            return FakeTransport([], {})
      +
      +        for key in ("HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY", "https_proxy", "http_proxy", "all_proxy", "TELEGRAM_PROXY", "NO_PROXY", "no_proxy"):
      +            monkeypatch.delenv(key, raising=False)
      +        monkeypatch.setattr(tnet.httpx, "AsyncHTTPTransport", factory)
      +
      +        custom_limits = httpx.Limits(
      +            max_connections=42,
      +            max_keepalive_connections=10,
      +            keepalive_expiry=30.0,
      +        )
      +        transport = tnet.TelegramFallbackTransport(
      +            ["149.154.167.220"], limits=custom_limits
      +        )
      +
      +        # 1 primary + 1 fallback = 2 AsyncHTTPTransport instances
      +        assert len(seen_kwargs) == 2
      +        for kw in seen_kwargs:
      +            assert "limits" in kw
      +            assert kw["limits"] is custom_limits
      +
       
       class TestFallbackTransportClose:
           @pytest.mark.asyncio

2 PRs closed without merging

From the close comments: merged via another change 2

  • #58803 fix(telegram): forward keepalive limits into fallback transport · merged via another change → 01ee312, #58982
  • #58804 fix(telegram): default fallback transport to platform keepalive limits (stop CLOSE_WAIT fd leak) · merged via another change → #58803, #58982
Closed issues · 1
  • Closed#58790bug(telegram): fallback-IP transport bypasses #31599 keepalive fix → general-pool CLOSE_WAIT fd leak
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#58982
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
5 Jul 2026

For your agent

The same facts, typed.