From 7134daaa34d70bc9ff1c00e8e5f355f599b53437 Mon Sep 17 00:00:00 2001 From: matanweks Date: Tue, 17 Mar 2026 22:40:20 +0200 Subject: [PATCH] fix(client): prevent ClientFactory.create() from mutating shared config extensions When `create()` was called with per-client extensions, the merged list was written back to `self._config.extensions`, permanently mutating the shared config. Extensions would silently accumulate across calls. Use `dataclasses.replace()` to create a per-call config copy instead of mutating the original, matching the immutability pattern from #744. Fixes #858 --- src/a2a/client/client_factory.py | 12 +++++++++--- tests/client/test_client_factory.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/a2a/client/client_factory.py b/src/a2a/client/client_factory.py index c3d5762eb..cc792b78c 100644 --- a/src/a2a/client/client_factory.py +++ b/src/a2a/client/client_factory.py @@ -1,5 +1,6 @@ from __future__ import annotations +import dataclasses import logging from collections.abc import Callable @@ -239,15 +240,20 @@ def create( all_extensions = self._config.extensions.copy() if extensions: all_extensions.extend(extensions) - self._config.extensions = all_extensions + + config = ( + dataclasses.replace(self._config, extensions=all_extensions) + if extensions + else self._config + ) transport = self._registry[transport_protocol]( - card, transport_url, self._config, interceptors or [] + card, transport_url, config, interceptors or [] ) return BaseClient( card, - self._config, + config, transport, all_consumers, interceptors or [], diff --git a/tests/client/test_client_factory.py b/tests/client/test_client_factory.py index c388974b1..1ed9f01c7 100644 --- a/tests/client/test_client_factory.py +++ b/tests/client/test_client_factory.py @@ -266,3 +266,20 @@ async def test_client_factory_connect_with_consumers_and_interceptors( call_args = mock_base_client.call_args[0] assert call_args[3] == [consumer1] assert call_args[4] == [interceptor1] + + +def test_client_factory_create_does_not_mutate_config_extensions( + base_agent_card: AgentCard, +): + """Verify that calling create() with extensions does not mutate the factory's config.""" + config = ClientConfig( + httpx_client=httpx.AsyncClient(), + extensions=['base-ext'], + ) + factory = ClientFactory(config) + + factory.create(base_agent_card, extensions=['ext-a']) + factory.create(base_agent_card, extensions=['ext-b']) + + # Config should be unchanged — no accumulation + assert config.extensions == ['base-ext']