fix(hass): stop the standalone watcher restarting on unrelated apps.yaml files - #4401
Conversation
…aml files (springfall2008#4397, springfall2008#4396) hass.py's file watcher (used by the shipped add-on's runtime loop, not just dev) walked its working directory tree and flagged any file whose basename was exactly "apps.yaml", anywhere in that tree - never checking it against the actual configured apps.yaml path. The new Annual prediction tool's headless work directory (annual_work/, inside config_root - /config in production) writes its own isolated apps.yaml-shaped scratch file on every WhatIf run, for legitimate reasons (an isolated headless PredBat instance needs its own config, per annual.py's create_headless_predbat()). But since it lands in the same tree the live watcher walks and happens to share the filename, the watcher couldn't tell it apart from the real config and force-restarted the live add-on mid-simulation. Extracted the file-collection logic into two testable functions: resolve_apps_yaml_path() (same PREDBAT_APPS_FILE resolution used when the config is actually loaded) and collect_watch_files(), which now only matches an "apps.yaml"-named file if its resolved path equals that one real path - any other file sharing the name elsewhere in the tree is correctly ignored. .py source files are unaffected. This fixes the *trigger* for the restart reported in springfall2008#4397/springfall2008#4396. It does not fix the separate, pre-existing "New install detected" / reset_inverter race in userinterface.py's load_user_config() that caused the reporter's actual settings loss once the restart happened - that's a real, separate hardening issue or any abrupt restart could still hit it, tracked as a follow-up.
There was a problem hiding this comment.
Pull request overview
This PR fixes an add-on restart bug in standalone-mode by tightening hass.py’s file-watcher logic so it only watches the configured apps.yaml (via PREDBAT_APPS_FILE resolution) rather than any same-named file found anywhere under the watched directory tree—preventing Annual WhatIf runs from triggering unrelated restarts.
Changes:
- Added
resolve_apps_yaml_path()andcollect_watch_files()to make watcher file selection explicit and testable. - Updated the standalone runtime loop to use the new watcher file collection (watch
.pyplus the resolved realapps.yamlonly). - Added a regression test reproducing the real-vs-decoy
apps.yamlscenario and wired it into the test registry.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| apps/predbat/hass.py | Refactors watcher file collection to avoid matching unrelated apps.yaml files under the watched tree. |
| apps/predbat/tests/test_hass_watcher.py | Adds a focused regression test that validates only the real configured apps.yaml is watched. |
| apps/predbat/unit_test.py | Registers the new watcher test in the unit test runner. |
…lked roots Copilot review on springfall2008#4401: collect_watch_files() only included the real configured apps.yaml if the os.walk over roots happened to encounter it. In production that's always true (cwd resolves to /config, and the file lives directly there), but if PREDBAT_APPS_FILE ever pointed somewhere outside "." / "/addon" entirely, config changes would silently stop being watched at all - defeating the watcher's original purpose. Now explicitly appends the resolved apps.yaml path after the walk if it exists on disk and wasn't already found. Added tests for both the outside-the-roots case and the configured-path-does-not-exist case.
|
Good catch, fixed - |
Summary
hass.py's file watcher (used by the shipped add-on's runtime loop, not just dev hot-reload) walked its working directory tree and flagged any file whose basename was exactlyapps.yaml, anywhere in that tree - never checking it against the actual configured apps.yaml path.The new Annual prediction tool's headless work directory (
annual_work/, insideconfig_root-/configin production) writes its own isolatedapps.yaml-shaped scratch file on every WhatIf run, for legitimate reasons (an isolated headless PredBat instance needs its own config -annual.py'screate_headless_predbat()/write_minimal_apps_yaml()). But since it lands in the same tree the live watcher walks and happens to share the filename, the watcher couldn't tell it apart from the real config file and force-restarted the live add-on mid-simulation.resolve_apps_yaml_path()(samePREDBAT_APPS_FILEresolution already used when the config is actually loaded, athass.py'sload_apps_yaml) andcollect_watch_files(), which now only matches anapps.yaml-named file if its resolved path equals that one real path - any other file sharing the name elsewhere in the tree is correctly ignored..pysource files (and dotfiles being excluded) are unaffected.test_hass_watcher.py) directly reproduces the bug scenario: a realapps.yamlat the root plus a decoyapps.yamlin a scratch subdirectory, confirming only the real one is watched.Scope note: this fixes the trigger for the restart reported in #4397/#4396. It does not fix the separate, pre-existing "New install detected" /
reset_inverterrace inuserinterface.py'sload_user_config()that actually caused the reporter's settings loss once the unplanned restart happened - that race isn't specific to the Annual tool and could in principle be hit by any abrupt restart (crash, OOM, etc.), so it deserves its own more careful fix rather than a quick patch bundled in here. Tracked as a follow-up.Test plan
./run_pre_commit(viacoverage/run_pre_commit) - all hooks passtest_hass_watcher- reproduces the exact bug scenario, confirms the real apps.yaml is watched, a same-named scratch file elsewhere is not, source files still are, dotfiles never are./run_all --quick) passesAddresses #4397, #4396