From d49635647ab6864339ff18d8e0fd11522ce6dbba Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 30 Sep 2026 13:52:15 +0200 Subject: [PATCH] Do not mutate generated zeroconf matchers in async_get_zeroconf (#183330) Co-authored-by: Claude --- homeassistant/loader.py | 7 ++++++- tests/test_loader.py | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index d301ce871cf0..9891109dd043 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -516,7 +516,12 @@ async def async_get_zeroconf( hass: HomeAssistant, ) -> dict[str, list[ZeroconfMatcher]]: """Return cached list of zeroconf types.""" - zeroconf: dict[str, list[ZeroconfMatcher]] = ZEROCONF.copy() # type: ignore[assignment] + # Copy the lists too, custom integrations append to them below + generated_zeroconf = cast(dict[str, list[ZeroconfMatcher]], ZEROCONF) + zeroconf = { + service_type: list(matchers) + for service_type, matchers in generated_zeroconf.items() + } integrations = await async_get_custom_components(hass) for integration in integrations.values(): diff --git a/tests/test_loader.py b/tests/test_loader.py index 2db38d7fa065..f2b764c8b79a 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -15,6 +15,7 @@ from homeassistant import loader from homeassistant.components import hue from homeassistant.components.hue import light as hue_light from homeassistant.core import HomeAssistant +from homeassistant.generated.zeroconf import ZEROCONF from homeassistant.helpers.json import json_dumps from homeassistant.util.json import json_loads @@ -895,6 +896,32 @@ async def test_get_zeroconf(hass: HomeAssistant) -> None: ] +async def test_get_zeroconf_does_not_mutate_generated(hass: HomeAssistant) -> None: + """Verify custom matchers for an existing type do not leak into the generated table.""" + integration = loader.Integration( + hass, + "custom_components.test_http", + None, + { + "name": "test_http", + "domain": "test_http", + "config_flow": True, + "dependencies": [], + "requirements": [], + "zeroconf": ["_http._tcp.local."], + }, + ) + generated = list(ZEROCONF["_http._tcp.local."]) + + with patch("homeassistant.loader.async_get_custom_components") as mock_get: + mock_get.return_value = {"test_http": integration} + await loader.async_get_zeroconf(hass) + zeroconf = await loader.async_get_zeroconf(hass) + + assert zeroconf["_http._tcp.local."] == [*generated, {"domain": "test_http"}] + assert ZEROCONF["_http._tcp.local."] == generated + + async def test_get_application_credentials(hass: HomeAssistant) -> None: """Verify that custom components with application_credentials are found.""" test_1_integration = _get_test_integration(hass, "test_1", True)