Skip to content

fix: Correct the supported Home Assistant floor and three cost defects - #18

Merged
sdebasek merged 1 commit into
mainfrom
fix/review-install-and-perf
Aug 21, 2026
Merged

sdebasek merged 1 commit into
mainfrom
fix/review-install-and-perf

Conversation

@sdebasek

Copy link
Copy Markdown
Owner

Second of three PRs from the multi-agent review: installation, CI and runtime cost. Merge after #17.

1. The declared Home Assistant floor was wrong, and shipped a broken install

hacs.json declared 2025.2.0. All eight platform modules import AddConfigEntryEntitiesCallback at module top level, and that name only exists from 2025.3.0. HACS gates installation on the declared floor, so a user on the 2025.2 line was allowed to install and update, and then every platform raised ImportError during async_forward_entry_setups. Nothing survived, not even the pushed event entities, since event.py imports the same name. They got a traceback instead of the clean "requires a newer Home Assistant" message HACS exists to produce.

The floor was established rather than assumed. Every from homeassistant... import across all 17 modules was AST-extracted and each symbol resolved against unpacked 2025.2.5 and 2025.3.0 wheels, checking top-level bindings, class members, function signatures and keyword arguments:

  • AddConfigEntryEntitiesCallback is the only symbol missing at 2025.2.5, and the entire import surface is satisfied at 2025.3.0. PyPI confirms 2025.2.5 is the last 2025.2 patch, so the floor is exactly 2025.3.0.
  • Nothing pushes it higher. Confirmed present at 2025.3.0: ConfigEntry[_DataT] with runtime_data (so both the attribute and the type alias work), DataUpdateCoordinator(config_entry=...), async_schedule_reload, ir.async_create_issue kwargs, er.RegistryEntryDisabler, dr.async_get_device(identifiers=...), get_url's five kwargs, all eight selectors, EventEntity, UpdateEntityFeature.INSTALL, ColorMode.RGB, ZeroconfServiceInfo.host, DhcpServiceInfo.
  • Cross-checked empirically: deleting that symbol from the installed 2026.8.2 makes 9 modules fail to import.

Fixed in hacs.json and README.md.

2. The weekly CI run could not do the job it was added for

The workflow's weekly schedule: exists, per its own comment and CONTRIBUTING, so that a Home Assistant release breaking the integration is caught without anyone pushing. It could not: requirements-test.txt pins pytest-homeassistant-custom-component, so every scheduled run installed the same Home Assistant and re-tested exactly what the last push tested.

Two new jobs, both scheduled or manual only, so neither can block a pull request for something a contributor did not cause:

  • latest HA installs pytest-homeassistant-custom-component unpinned and runs the suite. Chosen over continue-on-error on the pinned job, because that would hide a genuine break behind a green run.
  • minimum HA reads the floor back out of hacs.json, installs that Home Assistant, and imports every module, so point 1 cannot silently drift again. It installs homeassistant directly rather than adding a second pin that could drift from the claim it guards, and it imports rather than running the suite, since the tests target a core 18 months newer.

Both steps were dry-run locally: the floor step resolves to homeassistant==2025.3.0 and the import step imports all 17 modules and exits 0.

3. Setup blocked for about 20 seconds on an unreachable device

Two sequential 10 second timeouts, addressed separately:

  • Provisioning moves off the setup path into a task created just before platform forwarding, so platforms no longer queue behind it. On a healthy first setup this also stops nine provisioning round trips delaying entity creation. Deliberately a tracked task, not a background one: unload waits for a tracked task but cancels a background one, and a provisioning run cancelled partway leaves the device's slot table half written.
  • A device whose shape is already remembered gets a 3 second first deadline instead of 10, because that poll is only fetching values the ordinary poll brings 5 seconds later.

Offline restart of a known device: roughly 20s becomes 3s. First-ever setup of an offline device stays at 10s on purpose, since there the poll is the only thing that can ever create its polled entities. That asymmetry has its own test so it cannot be tidied away later.

Rejected: forwarding platforms before the refresh completes. last_update_success starts True, so entities built from the seeded cache would briefly publish remembered values as live state and pollute history.

4. The slow cadence counted refreshes, not seconds

SLOW_REFRESH_EVERY = 12 was documented as "5s x 12 = 1min" but counted refresh cycles, so every requested refresh (each settings write triggers one) advanced it and metadata was polled more often than claimed, at a rate that varied with user activity. Now SLOW_REFRESH_SECONDS = 60, compared against elapsed time. _force_full still short-circuits it, so writes are unaffected.

5. Healing retried forever

A device that can never accept provisioning, for example one whose slots are full of foreign actions, produced one ERROR line and one extra request every minute indefinitely. Backoff is now 1, 2, 4, 8 ... slow cycles up to about an hour, keyed on what the attempt would do (the missing callbacks plus the slot layout) rather than on the error type. So freeing a slot in the wBox app resets it and is acted on at the next cycle instead of at the end of a long wait, and a genuinely new problem is never delayed by an old failure. A repeated identical failure now logs at DEBUG, taking the documented case from about 1440 ERROR lines a day to one.

Not the approach the review suggested. Keying on InsufficientSlotsError alone would have left every other repeated failure retrying forever.

6. CONTRIBUTING claimed lint blocks the build

It does not, because branch protection decides and does not currently require it. The doc now says so plainly.

Action for you: mark ruff as a required status check on main. The full set should be hassfest, HACS, ruff, tests. minimum HA is deliberately not recommended as required yet: it installs an old release from PyPI, so an upstream packaging problem could block merges for an unrelated reason.

Testing

205 passing, 98% coverage counting branches, ruff clean, all YAML parses.

Seven new tests, five of them proven to fail against pre-fix code with the failure recorded. One backoff test was additionally checked against a wrong fix (backoff ignoring the slot layout) to confirm it catches that too. Two tests are flagged as behaviour-preservation guards rather than presented as regressions.

Not tested on hardware.

hacs.json declared 2025.2.0, but every platform module imports
AddConfigEntryEntitiesCallback, which Home Assistant only gained in
2025.3.0. HACS gates installation on that declared floor, so anyone on
the 2025.2 line was allowed to install and then had every platform fail
to import, including the event entities, leaving a traceback instead of
the clean "needs a newer Home Assistant" message HACS exists to give.
The floor was established by resolving every homeassistant import in the
package against 2025.2.5 and 2025.3.0 wheels, not by taking the review
at its word: that one symbol is the only thing missing at 2025.2.5, and
nothing in the import surface pushes it higher.

Two CI jobs now keep that honest. One installs the declared minimum and
imports every module, so the claim cannot drift again. One runs the
suite against the newest Home Assistant, which is what the weekly
schedule was documented to do and could not, because the test
requirements pin a single version, so every scheduled run re-tested
exactly what the last push had. Both are scheduled or manual only, so
neither can block a pull request for something a contributor did not do.

Setup no longer blocks for twenty seconds on an unreachable device.
Provisioning moves off the setup path, so platforms stop queueing behind
it, and a device whose shape is already remembered gets a shorter first
deadline because that poll is only fetching values the next one will
bring anyway. A device nothing is known about deliberately keeps the
full deadline: there, the poll is the only thing that can create its
entities at all.

The slow refresh cadence counted refreshes rather than seconds, so every
requested refresh advanced it and metadata was polled more often than
its own docstring claimed. It is now measured in elapsed time and named
for it.

Automatic-mode healing retried forever, one error line and one extra
request a minute for as long as the entry existed. It now backs off on
repeated failure, keyed on what the attempt would do rather than on the
error, so freeing a slot in the wBox app is acted on at the next cycle
instead of at the end of the backoff, and a genuinely new problem is
never delayed by an old one.

CONTRIBUTING no longer claims lint blocks the build, since branch
protection decides that and does not currently require it.
@sdebasek
sdebasek force-pushed the fix/review-install-and-perf branch from 6204de7 to 5980103 Compare August 21, 2026 11:50
@sdebasek
sdebasek merged commit 2a371a5 into main Aug 21, 2026
6 checks passed
@sdebasek
sdebasek deleted the fix/review-install-and-perf branch August 21, 2026 11:58
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