Skip to content

fix: return False on set_state timeout instead of raising - #159

Open
nsleigh wants to merge 1 commit into
Pyhass:devfrom
nsleigh:fix/state-change-timeout
Open

nsleigh wants to merge 1 commit into
Pyhass:devfrom
nsleigh:fix/state-change-timeout

Conversation

@nsleigh

@nsleigh nsleigh commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Summary

BaseDeviceHandler._execute_state_change is documented to return False on failure, but two failure paths raise instead:

  1. Timeout: a slow Hive API call lets asyncio.TimeoutError propagate out of session.api.set_state.
  2. Failed request: _call_endpoint returns {}, so resp["original"] raises KeyError.

Neither exception is a HomeAssistantError. So in Home Assistant, one slow device aborts a whole multi-entity service call, e.g. climate.set_hvac_mode across several TRVs, and the exception bypasses continue_on_error. The remaining devices never get the command.

Changes

src/helper/device_handler_base.py:

  • Catch asyncio.TimeoutError around set_state, log a warning and return False.
  • Read the status with resp.get("original") so a failed/empty response takes the existing "set_state failed" error path and returns False.

The timeout handling for polling is unchanged.

Behaviour change

tests/unit/test_base_handler.py previously asserted that a malformed response raises KeyError. That test is now test_malformed_set_state_response_returns_false, since raising contradicted the method's documented contract. I also added test_set_state_timeout_returns_false.

Testing

  • The full suite passes (1023 tests).
  • Hit in production. This fix is running in a live Home Assistant install with a real Hive account. Shortly after an HA restart, an automation called climate.set_hvac_mode on 11 TRVs in one service call, and the Hive API timed out on 5 of them:
    • Each timeout logged one line and the call carried on:
      WARNING ... device_handler_base: _execute_state_change - set_state timed out for <TRV name>
    • The service call and the automation run both completed without error, and the other 6 TRVs were updated normally.
    • Without this change, the first timeout would have raised asyncio.TimeoutError out of the service call. The TRVs after it would have been skipped and the automation run marked as errored.

🤖 Generated with Claude Code

_execute_state_change is documented to return False on failure, but a
Hive API timeout propagated as asyncio.TimeoutError, and a failed
request (for which _call_endpoint returns {}) raised KeyError on
resp["original"]. In Home Assistant this aborts the whole multi-entity
service call (e.g. climate.set_hvac_mode on several TRVs) and bypasses
continue_on_error, since neither is a HomeAssistantError.

Catch the timeout and read the status with .get() so both cases log
and return False as documented. Polling's own timeout handling is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.96%. Comparing base (91d96b4) to head (efa4db5).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##              dev     #159   +/-   ##
=======================================
  Coverage   99.96%   99.96%           
=======================================
  Files          30       30           
  Lines        2571     2576    +5     
  Branches      297      297           
=======================================
+ Hits         2570     2575    +5     
  Partials        1        1           
Flag Coverage Δ
unittests 99.96% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/helper/device_handler_base.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant