fix: don't drop locks when provider integration is still loading - #1322
Merged
Conversation
On a slow Home Assistant boot, LCM can set up before zwave_js reaches LOADED. The zwave_js node lookup then raises a raw ValueError that bypassed the base class's degraded-setup path entirely: the lock was popped from runtime data with no recovery listener registered, leaving every LCM entity unavailable until a manual reload. Three layers of fix: - zwave_js: translate the node-resolution ValueError into LockDisconnected so all call sites route it to the degraded/retry path instead of crashing. - base: async_setup_internal checks async_is_integration_connected() before any provider I/O and defers the capability probe and async_setup to the integration's LOADED transition, protecting every provider from untranslated startup exceptions. _setup_complete is now always set so shared-instance waiters can't hang on a failed setup. - base: the LOADED-transition path re-runs the same capability validation as initial setup (via the extracted _async_run_provider_setup), so a lock whose validation was deferred still gets the PIN-support check once its integration comes up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 7b1eefac5da6
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a cold-start race where Lock Code Manager initializes before a provider integration (notably zwave_js) reaches LOADED, previously causing an unhandled ValueError that dropped the lock and left entities unavailable until manual reload.
Changes:
- Translate
zwave_jsnode-resolutionValueErrorintoLockDisconnectedso startup failures follow the degraded/retry path. - Enforce a base-provider contract: defer provider I/O (capability probe + provider
async_setup) until the underlying integration is connected/loaded, and share the same validation logic on reconnect. - Add regression tests covering the unloaded-
zwave_jsstartup path, deferred setup/validation, and_setup_completesignaling behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| custom_components/lock_code_manager/providers/zwave_js.py | Map node lookup ValueError to LockDisconnected so startup races don’t drop locks. |
| custom_components/lock_code_manager/providers/_base.py | Defer provider setup until integration is connected; reuse validation/setup on LOADED transition; ensure _setup_complete is always set. |
| tests/providers/zwave_js/test_provider.py | Add unit test asserting unloaded zwave_js node lookup raises LockDisconnected. |
| tests/providers/zwave_js/test_e2e.py | Add end-to-end cold-start regression test ensuring degraded setup and automatic recovery on LOADED. |
| tests/providers/test_base.py | Add base-class tests for deferred provider I/O, reconnect validation parity, and _setup_complete behavior. |
| tests/providers/test_seam.py | Update seam tests to force “connected” so validation runs under the new deferred-setup contract. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1322 +/- ##
==========================================
+ Coverage 96.96% 97.00% +0.03%
==========================================
Files 53 53
Lines 6364 6373 +9
Branches 473 473
==========================================
+ Hits 6171 6182 +11
+ Misses 193 191 -2
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
5 tasks
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.
Proposed change
Fixes the cold-start race in #1321: when Home Assistant boots slowly, Lock Code Manager can set up before the zwave_js config entry reaches
LOADED. The zwave_js node lookup then raises a rawValueError(Device <id> config entry is not loaded) that bypassed the base class's degraded-setup machinery entirely — the lock was dropped from runtime data with no recovery listener registered, and every LCM entity stayed unavailable until a manual reload.The fix has three layers:
ValueErrorintoLockDisconnected, the provider contract's transport failure, so every call site (setup, coordinator refresh, writes) routes it to the existing degraded/retry path instead of treating it as a crash.async_setup_internalchecksasync_is_integration_connected()before attempting any provider I/O. If the provider integration isn't up yet, the capability probe andasync_setupare deferred to the integration'sLOADEDtransition — no provider can opt out of this protection by leaking an untranslated exception during startup._setup_completeis also now set unconditionally so shared-lock-instance waiters can't hang when setup fails structurally.LOADEDtransition handler re-runs the same capability validation as initial setup (extracted into_async_run_provider_setup), so a lock whose validation was deferred at boot still gets the PIN-support check once its integration comes up. A structural validation failure on reconnect is logged and leaves the provider un-setup rather than escaping into the reconnect task.New tests cover the exact reported failure (node lookup while the zwave_js entry is unloaded), the end-to-end cold-start scenario (LCM setup with zwave_js unloaded → lock survives degraded → recovers automatically when zwave_js loads), the base-class deferral, the deferred validation on reconnect (both PIN-supported and unsupported), and the
_setup_completeguarantee. Two existing seam tests that exercised capability validation through a never-loaded stub entry now force the connected signal, preserving their intent under the new contract.Type of change
Additional information
🤖 Generated with Claude Code