Skip to content

fix: don't drop locks when provider integration is still loading - #1322

Merged
raman325 merged 1 commit into
mainfrom
fix/zwave-js-setup-race-1321
Jul 4, 2026
Merged

fix: don't drop locks when provider integration is still loading#1322
raman325 merged 1 commit into
mainfrom
fix/zwave-js-setup-race-1321

Conversation

@raman325

@raman325 raman325 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

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 raw ValueError (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:

  1. zwave_js provider: node resolution now translates the ValueError into LockDisconnected, 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.
  2. Base class enforcement (all providers): async_setup_internal checks async_is_integration_connected() before attempting any provider I/O. If the provider integration isn't up yet, the capability probe and async_setup are deferred to the integration's LOADED transition — no provider can opt out of this protection by leaking an untranslated exception during startup. _setup_complete is also now set unconditionally so shared-lock-instance waiters can't hang when setup fails structurally.
  3. Recovery-path parity: the LOADED transition 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_complete guarantee. 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

  • 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

🤖 Generated with Claude Code

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
Copilot AI review requested due to automatic review settings July 4, 2026 01:36
@github-actions github-actions Bot added python Pull requests that update Python code bug Something isn't working labels Jul 4, 2026

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 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_js node-resolution ValueError into LockDisconnected so 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_js startup path, deferred setup/validation, and _setup_complete signaling 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.

Comment thread custom_components/lock_code_manager/providers/_base.py
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.00%. Comparing base (3a3a622) to head (53dad41).
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@            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     
Flag Coverage Δ
python 97.55% <100.00%> (+0.04%) ⬆️

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

Files with missing lines Coverage Δ
...om_components/lock_code_manager/providers/_base.py 97.12% <100.00%> (+0.22%) ⬆️
...components/lock_code_manager/providers/zwave_js.py 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@raman325
raman325 merged commit eeab3ed into main Jul 4, 2026
19 checks passed
@raman325
raman325 deleted the fix/zwave-js-setup-race-1321 branch July 4, 2026 01:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working python Pull requests that update Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ISSUE] zwave_js unhandled ValueError during initial lock setup when configured lock entries aren't loaded yet

2 participants