Skip to content

fix(providers): prevent startup race and false not connected errors (#708) - #709

Open
firstof9 wants to merge 1 commit into
FutureTense:mainfrom
firstof9:fix/708-startup-race-config-entry-setup
Open

fix(providers): prevent startup race and false not connected errors (#708)#709
firstof9 wants to merge 1 commit into
FutureTense:mainfrom
firstof9:fix/708-startup-race-config-entry-setup

Conversation

@firstof9

@firstof9 firstof9 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Prevent startup race condition and false 'Not Connected' / AttributeError errors when keymaster polls integration providers before their config entries have completed setup.

Proposed change

During Home Assistant startup, keymaster initializes and polls providers before target integration config entries (e.g., zwave_js, schlage) have completed async_setup_entry. This resulted in AttributeError on uninitialized runtime_data, false-alarm ERROR log messages, and temporary "Not Connected" status until integration loading finished.

  • ZWaveJSProvider & SchlageProvider:
    • Verify getattr(entry, "state", None) == ConfigEntryState.LOADED before attempting connection.
    • Safely retrieve runtime_data and client attributes, returning False cleanly and logging at DEBUG level when not yet loaded/available.
  • KeymasterCoordinator:
    • Log connection failures and not-connected lock state at DEBUG level when Home Assistant is still starting up (hass.is_running is False), avoiding noisy error logs during boot while preserving ERROR logs for genuine disconnections while running.
  • Unit Tests:
    • Added unit tests for not-loaded config entries and missing runtime data in test_zwave_js.py and test_schlage.py.
    • Added unit tests for coordinator startup vs runtime logging in test_coordinator.py.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (which adds functionality)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

@codecov-commenter

codecov-commenter commented Aug 19, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.10%. Comparing base (cdb4922) to head (0ee9cb7).
⚠️ Report is 230 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #709      +/-   ##
==========================================
+ Coverage   84.14%   94.10%   +9.96%     
==========================================
  Files          10       42      +32     
  Lines         801     5446    +4645     
  Branches        0       30      +30     
==========================================
+ Hits          674     5125    +4451     
- Misses        127      321     +194     
Flag Coverage Δ
python 94.00% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…utureTense#708)

* Check ConfigEntryState.LOADED and safely access runtime_data in ZWaveJSProvider and SchlageProvider
* Avoid error logging on connection failures and not-connected status while Home Assistant is starting up
* Add unit tests for config entry loading states and coordinator startup logging
@firstof9
firstof9 force-pushed the fix/708-startup-race-config-entry-setup branch from aada6bb to 0ee9cb7 Compare August 19, 2026 21:49

@secondof9 secondof9 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📋 Review Summary

Tip

Review Status: 🟢 APPROVED
Change Type: 🐛 Bug Fix
Review Effort: 🟢 Low
Core Impact: Solid startup-race fix using proper HA ConfigEntryState checks and startup-aware logging levels.


🚦 CI & Pipeline Health Summary

Check / Workflow Name Status Impact on Review
coverage ✅ PASSED 94.10% coverage (up from 84.14% base)
Pytest (3.14) ✅ PASSED All tests pass including 110 new startup-race test cases
Autolabel PR ✅ PASSED Correctly labeled as bugfix
HACS Validation ✅ PASSED Integration schema valid
Hassfest Validation ✅ PASSED Manifest passes Home Assistant spec
Prek ✅ PASSED Pre-commit checks pass
Autolabel PR ✅ PASSED

Note

CI Pipeline Clear: All GitHub Actions workflows completed successfully.


🔍 Architectural Walkthrough

coordinator.py — startup-aware logging
  • custom_components/keymaster/coordinator.py
    • _connect_and_update_lock: if self.hass.is_running gating ensures that provider connection failures during HA startup produce DEBUG-level logs instead of noisy ERROR-level logs. The pattern is correct — is_running is a pure property read, zero I/O.
    • _update_lock_data: Same is_running guard on the "Not Connected" error log. Prevents startup churn in the update loop. This is a clean, low-impact change that fixes real operator UX (no spammy logs at boot).
schlage.py — defensive ConfigEntryState checks
  • custom_components/keymaster/providers/schlage.py
    • async_connect: Replaced try/except (AttributeError, TypeError) with explicit ConfigEntryState.LOADED check via getattr(entry, "state", None). This is the canonical HA pattern — checking state instead of catching broad exceptions. The getattr(entry, "runtime_data", None) + null check is also correct.
    • async_is_connected: Similar state check. Critically, the hasattr(coordinator, "data") guard prevents AttributeError if the coordinator object was replaced with a stub (defensive against upstream HA changes).
    • All try/except blocks cleanly replaced with explicit state/data checks.
zwave_js.py — defensive ConfigEntryState checks
  • custom_components/keymaster/providers/zwave_js.py
    • async_connect: Same pattern as schlage — explicit ConfigEntryState.LOADED check, getattr(runtime_data, "client", None), and a None guard. Removed the broader except (KeyError, TypeError, AttributeError) in favor of explicit checks.
    • This is the stronger approach: explicit state checks tell you exactly what's wrong, while catching broad exceptions silently masks bugs.
tests/ — comprehensive startup-race coverage
  • tests/test_coordinator.py: New TestConnectAndUpdateLockStartup class with 4 test cases — covers both is_running=True (ERROR log) and is_running=False (DEBUG log) for both _connect_and_update_lock and _update_lock_data. Excellent coverage of the coordinator-level fix.
  • tests/providers/test_schlage.py: Added test_connect_schlage_entry_not_loaded and test_not_connected_schlage_entry_not_loaded. Existing test fixtures updated with ConfigEntryState.LOADED defaults for all test cases.
  • tests/providers/test_zwave_js.py: Added test_connect_zwave_entry_not_loaded, test_connect_runtime_data_missing, and test_connect_client_missing_on_runtime_data. All existing fixtures updated with state defaults.

Test coverage note: The existing test_connect_coordinator_not_available test in test_schlage.py uses MagicMock(spec_set=[]) which means entry.state would raise TypeError — this test was already covering the un-set state case. The new tests are cleaner with MagicMock() + explicit state = ConfigEntryState.SETUP_IN_PROGRESS, making the intent clearer.


✅ Verdict

All three diagnostic lenses pass cleanly. The changes follow HA conventions, use defensive getattr() patterns correctly, add explicit startup-aware logging, and come with thorough test coverage. No concerns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Startup race: keymaster polls zwave_js before config entry finishes setup, causing false 'Not Connected' errors

3 participants