Summary
WebhookSender.__aenter__ calls _get_client() which constructs self._client = httpx.AsyncClient(timeout=...) even when self._owns_client=True. The owned-client path in _send_bytes ignores self._client entirely — it builds a fresh transport-pinned client per request. The eagerly-constructed self._client then sits idle until __aexit__ → aclose() closes it.
Cost
Per async with sender: block: one unused httpx.AsyncClient allocation (a few hundred bytes + an httpcore.AsyncConnectionPool instance). No socket opened (httpx is lazy on first request), so no resource leak.
Not a correctness bug — flagged as cosmetic by python-expert in PR #297 review.
Proposed fix
Skip await self._get_client() in __aenter__ when self._owns_client=True:
async def __aenter__(self) -> WebhookSender:
if not self._owns_client:
await self._get_client() # operator-supplied path still expects this
return self
aclose() already guards on self._client is not None so the no-op case works.
References
Summary
WebhookSender.__aenter__calls_get_client()which constructsself._client = httpx.AsyncClient(timeout=...)even whenself._owns_client=True. The owned-client path in_send_bytesignoresself._cliententirely — it builds a fresh transport-pinned client per request. The eagerly-constructedself._clientthen sits idle until__aexit__ → aclose()closes it.Cost
Per
async with sender:block: one unusedhttpx.AsyncClientallocation (a few hundred bytes + anhttpcore.AsyncConnectionPoolinstance). No socket opened (httpx is lazy on first request), so no resource leak.Not a correctness bug — flagged as cosmetic by python-expert in PR #297 review.
Proposed fix
Skip
await self._get_client()in__aenter__whenself._owns_client=True:aclose()already guards onself._client is not Noneso the no-op case works.References
src/adcp/webhook_sender.py:262-285(__aenter__+_get_client)