Repository navigation
Fix/audit 2026 10 - #26
Merged
Merged
Conversation
180 test bodies drove coroutines through asyncio.get_event_loop().run_until_complete() from synchronous tests. get_event_loop() without a running loop is deprecated and raises on recent Python versions unless a plugin happens to have installed a loop first, which made the suite depend on collection order. The tests are now async functions run by pytest-asyncio (asyncio_mode = auto) and await the coroutines directly; the helpers they share became coroutines as well. No assertion changed. Audit: T1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schedulers were stored per day mode display label and the form was built from the flow's raw data instead of the localized defaults. On a French instance that never opened the Mapping step, the form offered the English labels (Home, Work, ...) while the coordinator ran on Maison/Travail, so schedulers_per_mode.get(day_mode) never matched: no scheduler was ever turned on and all of them were turned off on every mode change. Renaming a label broke the assignment the same way. - The schedulers form is built from the effective data and its fields are named after the stable mode key (schedulers_home, ...), translated in en/fr. - The coordinator resolves the active schedulers through day_mode_key. - Config entry v5 migration re-keys the stored map: current label, key in another case, or a default label of any supported language (the broken French case) all resolve to the key; unknown labels are kept and logged, labels resolving to the same key are merged. - Tests that froze the label-keyed format now expect keys; new tests cover the French instance that skipped Mapping, a rename, and the migration paths. Audit: B1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unavailable or unknown work calendar was read as "no event": a CalDAV or Google outage on a remote-work Tuesday switched the day to Work, ran the schedulers, then switched back once the calendar returned; an absence set by a calendar event was cancelled the same way. A missing calendar at boot also returned early, skipping the whole cover schedule. - A work or holiday calendar that is missing, unavailable or unknown no longer drives the mode: it is kept, the outage is warned about once and its end logged. - The cover schedule, its timers and the cover checks run whatever the calendar state. A schedule computed while the calendar was unreadable is provisional and computed again once the calendar answers, so a mode change at that point still applies to today's covers. - manifest: after_dependencies on calendar, so the calendar integration is set up first when both load at startup. Audit: B2, P4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'skip' cleared cover_open_time, and heat protection (proactive and reactive) as well as its window required it: in Away mode with 'skip' during a heatwave the south cover was never closed, on exactly the day nobody was home to do it. - 'skip' now only suppresses the opening. Heat protection's window starts at the opening time when there is one, otherwise at the sunrise-based time (sunrise floored at the earliest opening time, or that earliest time when sunrise is unknown). The precise timer fires at that start on skip days. - Heat protection sends nothing when every protected cover already reads closed: on a skip day they are typically still down from the night, and a My press or a stop would only move them up. Audit: B3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The store only kept the mode keys and the absence flag. A manual change with a 120 min override followed by an HA restart lost _override_until, and the first calendar sync after the reboot overwrote the mode picked by hand. The override end is now saved with the rest of the state and restored when it is still in the future. Audit: B4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover, button and switch services were called from the coordinator update without any error handling. A HomeAssistantError (a Somfy API timeout, a removed entity) failed the update, and every HomeShift select, sensor and binary sensor went unavailable until the next poll. The proactive close also marked the day as checked before closing, so a failed close was never retried that day. - Every cover/button call goes through one helper that logs a failure as a warning and reports it. The daily open, the daily close and both heat protection paths only record an action as done once it succeeded; a failure is retried on the next poll (close_cover is idempotent, a My press on a cover already at its position is a no-op). - Scheduler switches are now called with blocking=True inside the same kind of guard: a failure becomes visible in the log instead of being lost in a fire-and-forget task, and cannot fail the update. Audit: P1, P3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
async_config_entry_first_refresh() arms the next-mode and cover timers, but their cancellation was only registered with async_on_unload after the platforms were forwarded. When the first refresh failed after arming them, HA raised ConfigEntryNotReady and retried with a new coordinator while the old one's timers stayed armed, later running async_sync_calendar on an orphan coordinator (real switch and cover calls). The cancellations are now registered right after the coordinator is built; HA runs the callbacks registered so far when setup fails. Audit: P2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every entity hard-coded an English _attr_name, so the `entity` section of the translations was never used (and select.day_mode was even named "HomeShift" there). The device info dict was duplicated in each platform. - Entities use _attr_has_entity_name with a translation_key: names now follow the instance language. en.json gets the missing Cover Open Time name and the right Day Mode name. - Home Assistant derives a new entity's object id from the translated name for the languages it supports natively, French included, which would have registered select.homeshift_mode_jour on a French install. The entity id is therefore pinned to the existing English one (select.homeshift_day_mode, number.homeshift_override_duration, sensor.homeshift_next_mode, ...): new installs get the ids the README, automations and the HomeShift card expect, whatever the language. Existing registry entries are untouched. - One shared DeviceInfo, declared as a service device (no hardware). - First tests on a real Home Assistant instance (fixture hass): entity ids in en and fr, translated names, the service device. Audit: A1, A4 (device info) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cover open/close time sensors, the covers-left-open sensor and the heat protection sensor are only created while their feature is configured. Removing the feature stopped creating them, but their entity registry entries stayed behind as restored, unavailable entities. Each platform now removes the registry entries of the conditional entities it no longer creates. Covered by a reload-on-options test on a real Home Assistant instance. Audit: A5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- async_remove_entry deletes the two .storage files the coordinator and the cover manager keep per entry; they were left orphaned when the integration was deleted. - diagnostics.py exposes the entry configuration, the coordinator data and today's cover schedule for bug reports (no secret in there: entity ids, mode maps and times only). - manifest integration_type is now "service": HomeShift drives other entities and has no hardware, which "hub" implied. Audit: A4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The translations named the Away display field day_display_absence while the form generates day_display_away (field name built from the mode key), so the raw field name was shown. Renamed in en/fr, config and options. - The cover action and the per-mode opening time option labels were picked in Python from hass.config.language (French or English only, the opening labels English only). They now come from a "selector" section of the translations through the selectors' translation_key, like any other label. - Tests check that every generated display field and selector option has a translation in both languages. Audit: A2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- manifest: declare the astral requirement cover_manager imports directly (it only worked as a transitive dependency of HA core); a lower bound, so HA's own pin always satisfies it. Point documentation to Gamso/homeshift (was Gamso/day_mode), add issue_tracker, and sort the keys the way hassfest expects. - config_flow: the config flow and the options flow carried two copies of the same eight steps (~170 lines). They now share one mixin; each flow keeps only its entry point and its final step. - Remove the unused THERMOSTAT_OFF_KEY constant and its import. - Remove custom_components/homeassistant, a symlink to a path of the author's dev container. - hacs.json: drop the "domain" key, unknown to HACS and spelled home_shift. - .vscode: fix the leftover day_mode workspace path. Audit: A3, A6 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The suite drove the coordinator against a MagicMock hass, so nothing covered a real setup/unload, the reload on options, the timers of a failed first refresh, or the French setup path behind B1. - New tests on the hass fixture of pytest-homeassistant-custom-component: setup then unload (services removed, entities unavailable, timers cancelled), options saved -> entry reloaded with a new coordinator, a failing first refresh (SETUP_RETRY, no timer left armed), an unavailable calendar keeping the Remote mode then resuming, and the French config flow that skips Mapping and must still turn on the work-day scheduler. - The day_mode setter existed in the coordinator "for test setup only". It is gone; tests use a set_day_mode() helper from conftest instead. Audit: T1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The HomeShift card needs to open or close the managed covers when the opening or closing time is clicked. Nothing exposed that: the daily open and close only ran from the schedule. - New button platform, created under the same condition as the cover open/close time sensors (at least one daily cover): Open covers and Close covers, entity ids button.homeshift_open_covers and button.homeshift_close_covers (pinned like the other entities), unique ids <entry_id>_open_covers / <entry_id>_close_covers, translation keys open_covers / close_covers (en/fr), icons mdi:window-shutter-open / mdi:window-shutter. Plain buttons, not coordinator entities, so they stay available; removed from the registry once the daily covers are unconfigured. - New services homeshift.open_covers and homeshift.close_covers, without fields (services.yaml, en/fr translations), removed with the last entry like the other services. - Both reuse the cover manager's code: open_cover on every daily cover whatever the time or 'skip'; the close follows the evening close rules (cover behind an open window left up and warned about, My position button pressed instead of close_cover), now shared in one helper. They run under the same action lock, and a failed service call is logged, never raised. - Decision: a manual action does not mark the day's scheduled open or close as done and does not touch the covers-left-open warning. The scheduled actions still run at their time; an open command on open covers is a no-op, and a manual close in the morning should not silently cancel that day's opening. Documented in the README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CI only compiled the Python files and parsed the JSON ones, on actions/checkout@v3 and setup-python@v4. requirements_test.txt was not pinned and pytest had three configurations (pyproject.toml, setup.cfg, .devcontainer/pytest.ini). - validate.yml: hassfest (home-assistant/actions), the HACS action and a ruff job next to the existing validate.sh, on checkout@v5 and setup-python@v6. The syntax/JSON/file checks were already done by validate.sh and are no longer duplicated. - ruff configured in pyproject.toml (E, F, W, B, UP, line length 180, the repo's black setting), pinned in requirements_dev.txt; the findings are fixed (unused imports and variables, Callable from collections.abc, datetime.UTC, trailing whitespace in a script). - requirements_test.txt pins pytest-homeassistant-custom-component 0.13.363, i.e. Home Assistant 2026.9.0, the minimum hacs.json declares, plus icalendar. The suite also passes on 2026.10.0b0. - pyproject.toml is the only pytest configuration (testpaths added); setup.cfg and .devcontainer/pytest.ini are removed. - The 2x brand icon was named icon@2x.png.png; renamed icon@2x.png. Audit: T2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing here ships with the integration; these are the dev scripts and container the audit flagged. - scripts/init_calendars.py took the Home Assistant token as a command line argument, visible to every local user (ps, /proc/<pid>/cmdline) and kept in the shell history. It now reads HA_TOKEN from the environment, or prompts for it without echo. - devcontainer: ~/.ssh is mounted read-only. The container can still use the keys, but no longer modify them, authorized_keys or the SSH config. - scripts/starts_ha.sh downloaded the scheduler component and card without any integrity check, then ran them inside Home Assistant and the browser. Each download is now checked against a pinned SHA-256 and deleted on mismatch. The card's digest is the one GitHub publishes for the release asset; GitHub publishes none for the older scheduler.zip, so its pin is left empty: the script prints the computed digest with a warning until the author checks and pins it. Audit: S Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Entity ids: the README named select.day_mode, binary_sensor.covers_left_open, ... while the real ids carry the device prefix (select.homeshift_day_mode, ...). Fixed everywhere, including the notification automation example, which would never have triggered; states that the ids do not depend on the language. - The scan interval is a fixed 5 minutes, not a parameter; the holiday calendar is required, not optional; the day modes are four fixed keys that can be renamed, not a free list; the next-mode prediction looks 7 days ahead, not 2. - Documents the behaviour this branch changed: a calendar that cannot be read keeps the mode, covers keep running without it, schedulers are keyed by mode key (with the 1.9.x upgrade note), heat protection runs on 'skip' days from the sunrise-based time and leaves closed covers alone, failed commands are logged and retried, the manual override survives a restart, stores are removed with the entry, diagnostics, and the missing binary_sensor.homeshift_cover_heat_active entry. - .github/copilot-instructions.md: same corrections for contributors (file map, poll interval, test harness). Audit: D1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hassfest rejects it: "Requirement astral>=2.2 is a dependency of Home Assistant itself and must not be listed in the manifest of a custom integration". Keep using it undeclared and say why at the import. Audit: A3 (revised after CI) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some covers need to stay put for a while — a bedroom kept dark for a few days — without disabling the whole cover schedule. - homeshift.inhibit_covers takes covers out of the automation for a duration, until a date, or until resumed; homeshift.resume_covers hands them back (all of them when no cover is given). - An inhibited cover receives neither the daily open nor the evening close, its window is not checked, heat protection leaves it alone, and the open/close-now buttons and services skip it. Inhibiting or resuming never moves a cover; a missed open/close is not caught up, while heat protection stays armed for the rest of the day. - Inhibitions are persisted with the cover state, dropped at the first poll after they end, and listed in the diagnostics. - sensor.homeshift_covers_inhibited reports how many covers are inhibited, each one with its end, and every cover that can be inhibited, for dashboards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uVhrB9KRnmbFZRPHNHUev
test_names_follow_the_instance_language asserted that the day mode reads "Maison", which only holds on a weekend or a holiday: the test failed on any work day. It checks that names follow the instance language, so assert that the state is one of the French display names instead. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uVhrB9KRnmbFZRPHNHUev
On Saturday 2026-10-10 the covers opened at 07:44, the Remote time, instead of the 08:30 weekend time. At 00:00:00 the calendar entity still reported Friday's all-day "Télétravail" event — its state updates on its own schedule, a few seconds after the event ends — so the day mode stayed Remote and the day's schedule was computed with it. The switch to Home at the next poll did not move the opening time: the schedule was only computed once per day. - While the morning open has not run, a day-mode change (automatic or picked by hand) recomputes today's open time and reschedules the timers. This also covers the other direction: an event starting at midnight that the calendar entity only shows a poll later. Once the covers opened, a later change leaves the day's times alone. - A calendar event whose end_time is already past is ignored, so the midnight switch happens on time instead of five minutes later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uVhrB9KRnmbFZRPHNHUev
Gamso
force-pushed
the
fix/audit-2026-10
branch
from
October 10, 2026 06:06
1fbe129 to
f3dde9d
Compare
…close The evening close sent one close_cover to every cover at once, including the ones already lowered by hand. A cover that rejected that command made the whole call fail, and covers with a My position button were even raised back up by the press. Covers already reading closed now receive nothing, and each remaining cover gets its own call, so one failure no longer affects the others and the retry only resends to the covers still open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The main menu now lists Calendars, Day modes, Schedulers and Covers. Each entry shows a one-line summary of its current settings. - Covers is a sub-menu holding Managed covers, Opening and closing times, and Heat protection. - The menu lists the sections changed and not saved yet, and warns that closing with ✕ discards them. - Each form's button says where it returns: "Save and back to ...". - Day modes: the rules come first, the display names are folded, and the mode dropdowns show display names instead of raw keys. - Schedulers: one folded section per mode, titled with the mode's name and scheduler count. Chips show the friendly name only. - Heat protection and opening/closing times are split into sections: trigger/action, evening close/morning opening. - Managed covers are listed by name, and gain an "Edit a cover" entry. The stored options keep their flat format, so no migration is needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The evening close leaves a cover up when its window sensor reports the window open. A new option, "Close as soon as the window is shut" (off by default), lets that cover close the moment the window is shut, or go to its My position if it has a button. - The wait lasts until the next morning open and is only acted on at night, so past midnight still counts. A window shut in the daytime, even on a skip day, never lowers a cover. - The window sensors are listened to while the option is on. The poll catches up a window shut while Home Assistant was down, since the wait is persisted. - A cover lowered by hand or no longer managed stops waiting. An inhibited cover keeps waiting, and so does one whose window sensor is unavailable. - A cover closed this way drops off covers_left_open and the entities refresh at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… across upgrades - A thermostat mode change now switches the climates of tagged schedulers to the matching HVAC mode (or off) and runs the current slot of the enabled schedulers of that mode. - Covers left up by an open window at the evening close keep waiting for it to shut after an upgrade from a version that did not store the wait. - Diagnostics expose close_when_window_shuts and waiting_for_window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.