From c329bb4000fb9a7ef8873eee3eb6ee86b98facaa Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 3 Jun 2026 19:58:32 +0200 Subject: [PATCH] Don't log configuration errors when executing WS subscribe_trigger (#172918) --- .../components/websocket_api/commands.py | 19 +++- .../components/websocket_api/test_commands.py | 97 +++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 68a52fca9ab6..5ca6cd6ddce3 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -980,7 +980,24 @@ async def handle_subscribe_trigger( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Handle subscribe trigger command.""" - trigger_config = await async_validate_trigger_config(hass, msg["trigger"]) + # Validating the trigger config can fail on bad user input. Handle those + # errors here so they are reported to the client without being logged as + # unexpected errors by the default websocket error handler. + try: + trigger_config = await async_validate_trigger_config(hass, msg["trigger"]) + except vol.Invalid as err: + connection.send_error(msg["id"], const.ERR_INVALID_FORMAT, str(err)) + return + except HomeAssistantError as err: + connection.send_error( + msg["id"], + const.ERR_HOME_ASSISTANT_ERROR, + str(err), + translation_domain=err.translation_domain, + translation_key=err.translation_key, + translation_placeholders=err.translation_placeholders, + ) + return @callback def forward_triggers( diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 71771e355bdf..b987ce14dd72 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -2745,6 +2745,103 @@ async def test_subscribe_trigger( assert sum(hass.bus.async_listeners().values()) == init_count +@pytest.mark.parametrize( + ("trigger", "expected_error"), + [ + # Unknown trigger platform + ( + {"platform": "nonexistent"}, + { + "code": "invalid_format", + "message": "Invalid trigger 'nonexistent' specified", + }, + ), + # Missing mandatory config for the trigger platform + ( + {"platform": "numeric_state"}, + { + "code": "invalid_format", + "message": "required key not provided @ data['entity_id']", + }, + ), + # Unknown device, raised as a HomeAssistantError by the platform validator + ( + {"platform": "device", "domain": "light", "device_id": "nonexistent"}, + { + "code": "home_assistant_error", + "message": "Unknown device 'nonexistent'", + }, + ), + ], +) +async def test_subscribe_trigger_config_error( + hass: HomeAssistant, + websocket_client: MockHAClientWebSocket, + caplog: pytest.LogCaptureFixture, + trigger: dict, + expected_error: dict, +) -> None: + """Test trigger config errors are reported to the client without logging.""" + caplog.set_level(logging.ERROR) + + await websocket_client.send_json_auto_id( + {"type": "subscribe_trigger", "trigger": trigger} + ) + + msg = await websocket_client.receive_json() + assert msg["type"] == const.TYPE_RESULT + assert not msg["success"] + assert msg["error"] == expected_error + + # The expected error is not logged by the default websocket error handler + assert "Error handling message" not in caplog.text + + +async def test_subscribe_trigger_template_error_spams_log( + hass: HomeAssistant, + websocket_client: MockHAClientWebSocket, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that a failing trigger template spams the log. + + This documents unwanted behavior. Unlike subscribe_condition and + test_condition, subscribe_trigger does not suppress repeated template + variable errors: a trigger is evaluated event-driven by its platform (here + via async_track_template_result), outside any trace context, so the + trace-based suppression used for conditions does not apply. This should be + fixed in the future so the error is suppressed/forwarded instead of being + logged on every re-render. + """ + caplog.set_level(logging.WARNING) + hass.states.async_set("sensor.test", "1") + + await websocket_client.send_json_auto_id( + { + "type": "subscribe_trigger", + "trigger": { + "platform": "template", + "value_template": "{{ states('sensor.test') }}{{ undefined_variable }}", + }, + } + ) + + msg = await websocket_client.receive_json() + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + + # Each re-render of the template logs the undefined variable error again + for state in ("2", "3", "4"): + hass.states.async_set("sensor.test", state) + await hass.async_block_till_done() + + assert ( + caplog.text.count( + "Template variable warning: 'undefined_variable' is undefined" + ) + > 1 + ) + + async def test_test_condition( hass: HomeAssistant, websocket_client: MockHAClientWebSocket ) -> None: