Skip to content

Commit 7134daa

Browse files
committed
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
1 parent c0b38f0 commit 7134daa

2 files changed

Lines changed: 26 additions & 3 deletions

File tree

src/a2a/client/client_factory.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import dataclasses
34
import logging
45

56
from collections.abc import Callable
@@ -239,15 +240,20 @@ def create(
239240
all_extensions = self._config.extensions.copy()
240241
if extensions:
241242
all_extensions.extend(extensions)
242-
self._config.extensions = all_extensions
243+
244+
config = (
245+
dataclasses.replace(self._config, extensions=all_extensions)
246+
if extensions
247+
else self._config
248+
)
243249

244250
transport = self._registry[transport_protocol](
245-
card, transport_url, self._config, interceptors or []
251+
card, transport_url, config, interceptors or []
246252
)
247253

248254
return BaseClient(
249255
card,
250-
self._config,
256+
config,
251257
transport,
252258
all_consumers,
253259
interceptors or [],

tests/client/test_client_factory.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,20 @@ async def test_client_factory_connect_with_consumers_and_interceptors(
266266
call_args = mock_base_client.call_args[0]
267267
assert call_args[3] == [consumer1]
268268
assert call_args[4] == [interceptor1]
269+
270+
271+
def test_client_factory_create_does_not_mutate_config_extensions(
272+
base_agent_card: AgentCard,
273+
):
274+
"""Verify that calling create() with extensions does not mutate the factory's config."""
275+
config = ClientConfig(
276+
httpx_client=httpx.AsyncClient(),
277+
extensions=['base-ext'],
278+
)
279+
factory = ClientFactory(config)
280+
281+
factory.create(base_agent_card, extensions=['ext-a'])
282+
factory.create(base_agent_card, extensions=['ext-b'])
283+
284+
# Config should be unchanged — no accumulation
285+
assert config.extensions == ['base-ext']

0 commit comments

Comments
 (0)