Catch errors when evaluating automation conditions (#174799)

This commit is contained in:
Erik Montnemery
2026-06-27 11:28:27 +00:00
committed by Franck Nijhof
parent 1544ae83dd
commit a1e1b400f3
2 changed files with 211 additions and 12 deletions
+29 -12
View File
@@ -731,17 +731,32 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
trace_element = TraceElement(variables, trigger_path)
trace_append_element(trace_element)
if (
not skip_condition
and self._condition is not None
and not self._condition.async_check(variables=variables)
):
self._logger.debug(
"Conditions not met, aborting automation. Condition summary: %s",
trace_get(clear=False),
)
script_execution_set("failed_conditions")
return None
if not skip_condition and self._condition is not None:
try:
conditions_pass = self._condition.async_check(variables=variables)
except (vol.Invalid, HomeAssistantError) as err:
self._logger.error(
"Error while checking conditions of automation %s: %s",
self.entity_id,
err,
)
automation_trace.set_error(err)
return None
except Exception as err:
self._logger.exception(
"Unexpected error while checking conditions of automation %s",
self.entity_id,
)
automation_trace.set_error(err)
return None
if not conditions_pass:
self._logger.debug(
"Conditions not met, aborting automation. Condition summary: %s",
trace_get(clear=False),
)
script_execution_set("failed_conditions")
return None
self.async_set_context(trigger_context)
event_data = {
@@ -794,7 +809,9 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
)
automation_trace.set_error(err)
except Exception as err:
self._logger.exception("While executing automation %s", self.entity_id)
self._logger.exception(
"Unexpected error while executing automation %s", self.entity_id
)
automation_trace.set_error(err)
return None
+182
View File
@@ -7,6 +7,7 @@ from typing import Any
from unittest.mock import ANY, Mock, patch
import pytest
import voluptuous as vol
from homeassistant.components import automation, input_boolean, script
from homeassistant.components.automation import (
@@ -1930,6 +1931,187 @@ async def test_automation_with_error_in_script_2(
assert "string value is None" in caplog.text
@pytest.mark.parametrize(
("side_effect", "expected_error", "expect_traceback"),
[
(
HomeAssistantError("boom"),
"Error while executing automation automation.hello: boom",
False,
),
(
vol.Invalid("not valid"),
"Error while executing automation automation.hello: not valid",
False,
),
(
ValueError("unexpected"),
"Unexpected error while executing automation automation.hello",
True,
),
],
ids=["home_assistant_error", "voluptuous_invalid", "unexpected_exception"],
)
async def test_automation_with_error_in_action_script(
hass: HomeAssistant,
calls: list[ServiceCall],
caplog: pytest.LogCaptureFixture,
hass_ws_client: WebSocketGenerator,
side_effect: Exception,
expected_error: str,
expect_traceback: bool,
) -> None:
"""Test errors raised while running the action script are handled and traced."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"id": "hello",
"alias": "hello",
"trigger": {"platform": "event", "event_type": "test_event"},
"action": {"action": "test.automation"},
}
},
)
with patch(
"homeassistant.helpers.script.Script.async_run",
side_effect=side_effect,
):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(calls) == 0
assert expected_error in caplog.text
# A HomeAssistantError/voluptuous error is logged without a traceback, an
# unexpected error is logged with a traceback.
assert ("Traceback" in caplog.text) is expect_traceback
# The error is recorded on the automation trace.
client = await hass_ws_client()
await client.send_json_auto_id(
{"type": "trace/list", "domain": "automation", "item_id": "hello"}
)
response = await client.receive_json()
assert response["success"]
traces = response["result"]
assert len(traces) == 1
assert traces[0]["error"] == str(side_effect)
@pytest.mark.parametrize(
("side_effect", "expected_error", "expect_traceback"),
[
(
HomeAssistantError("boom"),
"Error while checking conditions of automation automation.hello: boom",
False,
),
(
vol.Invalid("not valid"),
"Error while checking conditions of automation automation.hello: not valid",
False,
),
(
ValueError("unexpected"),
"Unexpected error while checking conditions of automation automation.hello",
True,
),
],
ids=["home_assistant_error", "voluptuous_invalid", "unexpected_exception"],
)
async def test_automation_with_error_in_condition(
hass: HomeAssistant,
calls: list[ServiceCall],
caplog: pytest.LogCaptureFixture,
hass_ws_client: WebSocketGenerator,
side_effect: Exception,
expected_error: str,
expect_traceback: bool,
) -> None:
"""Test errors raised while checking conditions are handled and traced."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"id": "hello",
"alias": "hello",
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
"condition": "state",
"entity_id": "test.entity",
"state": "on",
},
"action": {"action": "test.automation"},
}
},
)
with patch(
"homeassistant.helpers.condition.ConditionsChecker.async_check",
side_effect=side_effect,
):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
# The action must not run when the condition check raises.
assert len(calls) == 0
assert expected_error in caplog.text
# A HomeAssistantError/voluptuous error is logged without a traceback, an
# unexpected error is logged with a traceback.
assert ("Traceback" in caplog.text) is expect_traceback
# The error is recorded on the automation trace.
client = await hass_ws_client()
await client.send_json_auto_id(
{"type": "trace/list", "domain": "automation", "item_id": "hello"}
)
response = await client.receive_json()
assert response["success"]
traces = response["result"]
assert len(traces) == 1
assert traces[0]["error"] == str(side_effect)
async def test_automation_with_error_in_condition_continues_after_recovery(
hass: HomeAssistant,
calls: list[ServiceCall],
) -> None:
"""Test the automation still runs once the condition stops raising."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"alias": "hello",
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
"condition": "state",
"entity_id": "test.entity",
"state": "on",
},
"action": {"action": "test.automation"},
}
},
)
hass.states.async_set("test.entity", "on")
with patch(
"homeassistant.helpers.condition.ConditionsChecker.async_check",
side_effect=HomeAssistantError("boom"),
):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(calls) == 0
# Without the error, the condition passes and the action runs.
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
assert len(calls) == 1
async def test_automation_restore_last_triggered_with_initial_state(
hass: HomeAssistant,
) -> None: