Skip to content

fix(entities): run remaining timer callbacks on the event loop - #13

Merged
buggedcom merged 3 commits into
mainfrom
fix/thread-safe-timer-callbacks
Aug 24, 2026
Merged

fix(entities): run remaining timer callbacks on the event loop#13
buggedcom merged 3 commits into
mainfrom
fix/thread-safe-timer-callbacks

Conversation

@buggedcom

Copy link
Copy Markdown
Owner

Problem

Recent Home Assistant cores promote non-thread-safe state writes from a warning to a hard RuntimeError, which crashes the update. Three timer handlers were registered with async_track_time_interval / async_call_later as plain functions, so HA runs them in a worker thread:

  • binary_sensor.py_on_time_interval → calls async_write_ha_state() (this is the crash users are hitting in the field)
  • sensor.py_handle_scan_tick → calls async_create_task() (loop-only)
  • sensor.py_start (the async_call_later callback) → calls async_track_time_interval / async_on_remove (loop-only)

Observed error:

RuntimeError: Detected that custom integration 'hass_datapoints' calls
async_write_ha_state from a thread other than the event loop ...
binary_sensor.py, line 127

Fix

Mark all three handlers @callback so HA schedules them on the event loop, where these calls are safe. All three do only non-blocking, loop-affine work, so @callback is correct (no blocking I/O moved onto the loop).

The store-listener path (_on_store_update) was audited and is fine_notify_listeners() is only ever invoked from loop coroutines.

Relationship to #11

#11 fixed the same bug class for sensor._handle_time_interval. This completes the sweep for the remaining three async_track_time_interval / async_call_later registrations.

Also

Bumps manifest.json version 0.5.10.6.1 (it had drifted behind the released tags).

🤖 Generated with Claude Code

Oliver Lillie and others added 3 commits August 24, 2026 22:26
…unsafe writes

_on_time_interval (binary_sensor), _handle_scan_tick and _start (sensor)
were registered with async_track_time_interval / async_call_later as plain
functions, so HA ran them in a worker thread. Calling async_write_ha_state()
and async_create_task() from a non-loop thread raises a non-thread-safe
RuntimeError on recent HA cores (previously only a warning), crashing the
update. Marking them @callback makes HA run them on the event loop.

Companion to #11, which fixed the same class of bug in _handle_time_interval.
Also bumps manifest version 0.5.1 -> 0.6.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Blank line at sensor.py:147 carried trailing whitespace introduced in #11,
which failed `ruff format --check` / `ruff check` in CI. Removing it makes
Lint / Python green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/build.sh injects manifest.json's version into the bundle banner,
so bumping the manifest to 0.6.1 left the committed hass-datapoints-cards.js
stale (v0.5.1), failing the "Check built JS is up-to-date" CI job. Regenerate
the version token to v0.6.1 and align package.json to match the release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@buggedcom
buggedcom merged commit dd05e4c into main Aug 24, 2026
15 checks passed
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