Skip to content

fix: recover zero-slot Z-Wave locks, keep failed locks visible, stop clobbering saved options#1344

Merged
raman325 merged 1 commit into
mainfrom
fix/zwave-caps-recovery-and-degraded-locks
Jul 16, 2026
Merged

fix: recover zero-slot Z-Wave locks, keep failed locks visible, stop clobbering saved options#1344
raman325 merged 1 commit into
mainfrom
fix/zwave-caps-recovery-and-degraded-locks

Conversation

@raman325

Copy link
Copy Markdown
Owner

Proposed change

Three fixes for the re-included-lock failure mode reported on the community forum (a factory-reset BE469ZP that LCM "wouldn't accept," which also "dropped" a sibling lock):

  1. zwave_js: one-shot recovery before concluding zero PIN slots. The driver derives a User Code CC lock's slot count from the cached supportedUsers value, defaulting to 0 when it is missing from the value DB — the state a battery lock lands in when its interview completes while asleep (common right after a factory-reset re-inclusion, [ISSUE] _uc_fallback_capabilities gives up silently when User Code CC values are missing from the DB (incomplete interview) → misleading "does not advertise PIN credential support" #1298). UserCodeCC.refreshValues reads that same cached value rather than re-querying it, so the only primitive that repopulates it is the CC API getUsersCount device query: its solicited UsersNumberReport persists supportedUsers. The provider now invokes it once and re-reads capabilities before raising the actionable structural error.

  2. providers: structural validation failures degrade instead of dropping. A LockCodeManagerProviderError during initial setup used to propagate, making _async_setup_new_locks pop the lock from runtime data — invisible in the UI, no repair issue, and no retry path until a full reload (the config-entry state listener was never registered). Initial setup now matches the reconnect path: the failure is logged, the coordinator/entities/recovery listener are still created, and the LOADED transition revalidates. The capability cache is invalidated on validation failure so revalidation re-probes instead of serving the degenerate read forever.

  3. init: the data→options migration merges options-preferred instead of clobbering. An options-flow save made while the entry was failed (e.g. right after a Z-Wave exclusion removed a configured lock entity — no update listener registered in that state) sat unprocessed in options, and the next successful setup overwrote it wholesale with stale data, silently discarding the user's fix. The migration now merges with options winning, matching EntryConfig.from_entry precedence.

A follow-up PR adds a repair issue so structural setup failures are visible in the UI.

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

…clobbering saved options

Three fixes for the re-included-lock failure mode reported on the
community forum (a factory-reset BE469ZP that LCM "wouldn't accept"):

1. zwave_js: when the capability probe reports a PIN type with zero
   slots (User Code CC interview data missing from the driver value DB,
   e.g. the interview completed while the lock was asleep after a
   re-inclusion), invoke the User Code CC API getUsersCount once and
   re-read capabilities before raising. The solicited UsersNumberReport
   persists supportedUsers in the driver's value DB; refreshValues
   cannot repair this state because it reads the same cached value.

2. providers: a structural validation failure during initial setup now
   degrades the lock (same outcome as the reconnect path) instead of
   propagating and getting the lock popped from runtime data with no
   retry path and no UI surface. The capability cache is invalidated on
   validation failure so revalidation on the provider integration's
   LOADED transition re-probes instead of serving the degenerate read.

3. init: the data->options migration now merges options-preferred
   instead of overwriting options with stale data. An options-flow save
   made while the entry was failed (no update listener registered) was
   silently discarded on the next successful setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 06bd50e78ebb
Copilot AI review requested due to automatic review settings July 16, 2026 02:48

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added python Pull requests that update Python code bug Something isn't working labels Jul 16, 2026
@raman325
raman325 merged commit 194c92b into main Jul 16, 2026
13 checks passed
@raman325
raman325 deleted the fix/zwave-caps-recovery-and-degraded-locks branch July 16, 2026 03:18
raman325 added a commit that referenced this pull request Jul 16, 2026
…-repair

* origin/main:
  fix: recover zero-slot Z-Wave locks, keep failed locks visible, stop clobbering saved options (#1344)
  build(deps-dev): bump markdownlint-cli from 0.49.0 to 0.49.1 (#1343)
  build(deps-dev): bump matter-python-client from 1.2.4 to 1.2.5 (#1342)
  [pre-commit.ci] pre-commit autoupdate (#1340)
  build(deps-dev): bump matter-python-client from 1.2.1 to 1.2.4 (#1338)
  build(deps): bump mypy from 2.2.0 to 2.3.0 (#1339)
  build(deps): bump the homeassistant group with 2 updates (#1337)

# Conflicts:
#	custom_components/lock_code_manager/providers/_base.py
#	tests/providers/test_base.py
raman325 added a commit that referenced this pull request Jul 16, 2026
…stale options

Follow-ups from the post-#1344/#1345 review:

- sync: a lock whose provider setup has not validated (deferred or
  structurally failed) now suspends its sync managers instead of
  attempting writes. Writes against such a lock could only fail on the
  capability probe, landing in the generic error handler with a
  misleading "report this bug" suspension repair (or, on matter,
  disabling the slot) stacked on the accurate lock_setup_failed repair
  -- and, on Z-Wave, re-firing the slot-count recovery device query on
  every attempt. Reconciliation resumes via request_sync_check once the
  LOADED-transition revalidation succeeds. New public
  BaseLock.provider_setup_succeeded property backs the gate.

- providers: reset _setup_succeeded when revalidation fails on the
  reconnect path, so a previously-healthy lock that degrades (e.g. a
  re-interview zeroed its slot count) re-gates sync instead of keeping
  stale success state.

- config_flow: the reauth write now consumes pending options
  (options-preferred, its own input winning) and clears them. An
  options-flow save made while the entry was failed would otherwise
  override the newer reauth fix on the next load, because the
  data->options migration merges options-preferred.

- zwave_js: guard the recovery's root-endpoint lookup with .get(0) so a
  KeyError cannot escape past the typed handlers and drop the lock.

- tests: collapse the duplicated zero-slot/healthy capability dict
  builders into one _user_code_caps(num_slots) factory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.98%. Comparing base (f4eebd3) to head (16e9fe2).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1344   +/-   ##
=======================================
  Coverage   96.97%   96.98%           
=======================================
  Files          53       53           
  Lines        6454     6470   +16     
  Branches      473      473           
=======================================
+ Hits         6259     6275   +16     
  Misses        195      195           
Flag Coverage Δ
python 97.52% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
custom_components/lock_code_manager/__init__.py 95.91% <ø> (ø)
...om_components/lock_code_manager/providers/_base.py 97.14% <100.00%> (+0.01%) ⬆️
...components/lock_code_manager/providers/zwave_js.py 99.32% <100.00%> (+0.03%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants