Skip to content

fix(hass): stop the standalone watcher restarting on unrelated apps.yaml files - #4401

Merged
springfall2008 merged 2 commits into
springfall2008:mainfrom
chalfontchubby:fix/watcher-scratch-file-restart
Jul 31, 2026
Merged

fix(hass): stop the standalone watcher restarting on unrelated apps.yaml files#4401
springfall2008 merged 2 commits into
springfall2008:mainfrom
chalfontchubby:fix/watcher-scratch-file-restart

Conversation

@chalfontchubby

Copy link
Copy Markdown
Collaborator

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 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 - annual.py's create_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.

  • Extracted the file-collection logic into two testable functions: resolve_apps_yaml_path() (same PREDBAT_APPS_FILE resolution already used when the config is actually loaded, at hass.py's load_apps_yaml) 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 (and dotfiles being excluded) are unaffected.
  • New test (test_hass_watcher.py) directly reproduces the bug scenario: a real apps.yaml at the root plus a decoy apps.yaml in 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_inverter race in userinterface.py's load_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 (via coverage/run_pre_commit) - all hooks pass
  • New test_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
  • Full quick suite (./run_all --quick) passes

Addresses #4397, #4396

…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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() and collect_watch_files() to make watcher file selection explicit and testable.
  • Updated the standalone runtime loop to use the new watcher file collection (watch .py plus the resolved real apps.yaml only).
  • Added a regression test reproducing the real-vs-decoy apps.yaml scenario 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.

Comment thread apps/predbat/hass.py
…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.
@chalfontchubby

Copy link
Copy Markdown
Collaborator Author

Good catch, fixed - collect_watch_files() now explicitly appends the resolved apps.yaml path after the walk if it exists on disk and wasn't already found, so it's always watched regardless of whether PREDBAT_APPS_FILE happens to point somewhere the roots would have reached. Added tests for both that case and a configured-path-does-not-exist case. All tests + pre-commit pass.

@springfall2008
springfall2008 merged commit 925f338 into springfall2008:main Jul 31, 2026
2 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.

3 participants