From 0bde45a2e7e036ddb46c05c013021cf0cfad49b2 Mon Sep 17 00:00:00 2001 From: atty57 <99388680+atty57@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:55:27 -0400 Subject: [PATCH 1/2] Python: Fix A2AAgent AttributeError on exit with a caller-supplied http_client `A2AAgent.__init__` assigned `_close_http_client` on only two of its three constructor paths. Passing `http_client=` without `client=` left the attribute unset, so `__aexit__` raised `AttributeError` when the agent was used as an async context manager - the exact usage the `http_client` docstring invites. Default `_close_http_client` to False before the branching logic. Every path now has a safe value, and a caller-supplied client is correctly left open for its owner to close. Fixes #7950 --- python/packages/a2a/agent_framework_a2a/_agent.py | 2 ++ python/packages/a2a/tests/test_a2a_agent.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/python/packages/a2a/agent_framework_a2a/_agent.py b/python/packages/a2a/agent_framework_a2a/_agent.py index 3d5ac41e86..6905d1a9c0 100644 --- a/python/packages/a2a/agent_framework_a2a/_agent.py +++ b/python/packages/a2a/agent_framework_a2a/_agent.py @@ -263,6 +263,8 @@ def __init__( super().__init__(id=id, name=name, description=description, **kwargs) self._http_client: httpx.AsyncClient | None = http_client + # Only close the httpx client if we created it; a caller-supplied one stays the caller's. + self._close_http_client = False self._timeout_config = self._create_timeout_config(timeout) bindings = supported_protocol_bindings if supported_protocol_bindings is not None else ["JSONRPC"] if client is not None: diff --git a/python/packages/a2a/tests/test_a2a_agent.py b/python/packages/a2a/tests/test_a2a_agent.py index 8ff147f98a..2a0205cb49 100644 --- a/python/packages/a2a/tests/test_a2a_agent.py +++ b/python/packages/a2a/tests/test_a2a_agent.py @@ -465,6 +465,16 @@ async def test_context_manager_no_cleanup_when_no_http_client() -> None: pass +async def test_context_manager_does_not_close_caller_supplied_http_client() -> None: + """A caller-supplied http_client (without client=) must survive __aexit__. See issue #7950.""" + mock_http_client = AsyncMock() + + async with A2AAgent(url="http://localhost:9999/", http_client=cast(Any, mock_http_client)): + pass + + mock_http_client.aclose.assert_not_called() + + def test_prepare_message_for_a2a_with_multiple_contents() -> None: """Test conversion of Message with multiple contents.""" From c0277441c9814e62405984a4345b385d814532a4 Mon Sep 17 00:00:00 2001 From: Atharva Vichare <99388680+atty57@users.noreply.github.com> Date: Sun, 30 Aug 2026 22:43:48 -0400 Subject: [PATCH 2/2] Comment changes Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- python/packages/a2a/agent_framework_a2a/_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/a2a/agent_framework_a2a/_agent.py b/python/packages/a2a/agent_framework_a2a/_agent.py index 6905d1a9c0..435f204030 100644 --- a/python/packages/a2a/agent_framework_a2a/_agent.py +++ b/python/packages/a2a/agent_framework_a2a/_agent.py @@ -263,7 +263,7 @@ def __init__( super().__init__(id=id, name=name, description=description, **kwargs) self._http_client: httpx.AsyncClient | None = http_client - # Only close the httpx client if we created it; a caller-supplied one stays the caller's. + # By default, leave a caller-supplied HTTP client open; specific paths may override this below. self._close_http_client = False self._timeout_config = self._create_timeout_config(timeout) bindings = supported_protocol_bindings if supported_protocol_bindings is not None else ["JSONRPC"]