Log a warning when a delayed voice command fails (#182492)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Michael Hansen
2026-09-19 21:21:32 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 920e59e65a
commit 0b034a6d29
2 changed files with 102 additions and 13 deletions
+28 -13
View File
@@ -457,20 +457,8 @@ class TimerManager:
timer.finish()
if timer.conversation_command:
from homeassistant.components.conversation import ( # noqa: PLC0415
async_converse,
)
self.hass.async_create_background_task(
async_converse(
self.hass,
timer.conversation_command,
conversation_id=None,
context=Context(),
language=timer.language,
agent_id=timer.conversation_agent_id,
device_id=timer.device_id,
),
self._async_run_conversation_command(timer),
"timer assist command",
)
elif timer.device_id in self.handlers:
@@ -483,6 +471,33 @@ class TimerManager:
timer.device_id,
)
async def _async_run_conversation_command(self, timer: TimerInfo) -> None:
"""Run the delayed command of a finished timer."""
from homeassistant.components.conversation import ( # noqa: PLC0415
async_converse,
)
assert timer.conversation_command is not None
result = await async_converse(
self.hass,
timer.conversation_command,
conversation_id=None,
context=Context(),
language=timer.language,
agent_id=timer.conversation_agent_id,
device_id=timer.device_id,
)
# Nothing is listening to the response, so an error is only visible here.
if result.response.response_type is intent.IntentResponseType.ERROR:
_LOGGER.warning(
"Delayed command failed: command=%s, code=%s, response=%s",
timer.conversation_command,
result.response.error_code,
result.response.speech.get("plain", {}).get("speech", ""),
)
def is_timer_device(self, device_id: str) -> bool:
"""Return True if device has been registered to handle timer events."""
return device_id in self.handlers
+74
View File
@@ -1,6 +1,8 @@
"""Tests for intent timers."""
import asyncio
from collections.abc import Callable
import logging
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@@ -32,6 +34,9 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
DELAYED_COMMAND = "turn on the lights"
DELAYED_COMMAND_ERROR = "Sorry, I am not aware of any device called lights"
@pytest.fixture
async def init_components(hass: HomeAssistant) -> None:
@@ -1526,6 +1531,75 @@ async def test_start_timer_with_conversation_command(
assert mock_converse.call_args.args[1] == test_command
def _delayed_command_acted() -> intent.IntentResponse:
"""Return the response of a delayed command that acted."""
return intent.IntentResponse(language="en")
def _delayed_command_failed() -> intent.IntentResponse:
"""Return the response of a delayed command that could not act."""
response = intent.IntentResponse(language="en")
response.async_set_error(
intent.IntentResponseErrorCode.NO_VALID_TARGETS, DELAYED_COMMAND_ERROR
)
return response
@pytest.mark.usefixtures("init_components")
@pytest.mark.parametrize(
("make_response", "expected_warnings"),
[
pytest.param(_delayed_command_acted, [], id="command_acted"),
pytest.param(
_delayed_command_failed,
[
f"Delayed command failed: command={DELAYED_COMMAND},"
f" code=no_valid_targets, response={DELAYED_COMMAND_ERROR}"
],
id="command_failed",
),
],
)
async def test_start_timer_conversation_command_result_logged(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
make_response: Callable[[], intent.IntentResponse],
expected_warnings: list[str],
) -> None:
"""Test that a delayed command which could not act is logged.
Nothing listens to the response of a delayed command, so an error is
otherwise invisible.
"""
with patch(
"homeassistant.components.conversation.async_converse",
return_value=conversation.ConversationResult(response=make_response()),
):
result = await intent.async_handle(
hass,
"test",
intent.INTENT_START_TIMER,
{
"seconds": {"value": 0},
"conversation_command": {"value": DELAYED_COMMAND},
},
device_id="test_device",
conversation_agent_id="test_agent",
)
assert result.response_type is intent.IntentResponseType.ACTION_DONE
# The delayed command runs in a background task
await hass.async_block_till_done(wait_background_tasks=True)
assert [
record.getMessage()
for record in caplog.records
if record.name == "homeassistant.components.intent.timers"
and record.levelno == logging.WARNING
] == expected_warnings
async def test_start_timer_with_sentence_trigger_validation(
hass: HomeAssistant, init_components
) -> None: