Describe the bug
NOTE: This was generated by Claude after resolving issues with keymaster on my environment (2 of 2 issues identified)
Environment
keymaster v0.5.1 (HACS)
Home Assistant 2026.5.4, Z-Wave JS (core)
Lock: Allegion/Schlage BE468 (ZWaveJS provider)
Symptom — reloading the keymaster config entry (Settings → Devices & Services → keymaster → Reload) crashes during unload and leaves the entry stuck in UNLOAD_IN_PROGRESS/SETUP_IN_PROGRESS; all of the lock's entities go unavailable and only a full HA restart recovers them:
ERROR [homeassistant.core] Unable to remove unknown job listener (<Job onetime listen homeassistant_started ... KeymasterCoordinator._create_listeners ...>)
ERROR [homeassistant.config_entries] Error unloading entry Side Door for keymaster
Traceback (most recent call last):
File ".../custom_components/keymaster/init.py", line 258, in async_unload_entry
await kmlock.provider.async_unload()
File ".../custom_components/keymaster/providers/_base.py", line 181, in async_unload
ValueError: list.remove(x): x not in list
Analysis — BaseLockProvider.async_unload() (providers/_base.py) unconditionally calls every stored unsub:
async def async_unload(self) -> None:
# Unsubscribe all listeners
for unsub in self._listeners:
unsub() # line 181
self._listeners.clear()
One of the unsub() callbacks performs a list.remove() on a listener that has already been removed (double-unsubscribe, or it was never registered), raising ValueError. This is the well-known HA listener-lifecycle pattern (cf. home-assistant/core#95823, frenck/spook#760). Because it raises mid-loop, the remaining listeners aren't cleaned up and the entry fails to unload.
Repro: reload the keymaster integration (no special state required).
Suggested fixes
Guard the unsub loop so one bad unsub can't abort cleanup:
for unsub in self.listeners:
try:
unsub()
except ValueError:
pass
self.listeners.clear()
And/or make listener registration/removal idempotent so each unsub is invoked exactly once (and isn't tracked after it's been called).
Impact: this also blocks recovery from other provider issues — the normal "reload to fix" path is unavailable, forcing a full restart. Discovered while trying to reload to recover from companion issue #_.
Home Assistant version
Home Assistant 2026.5.4, Z-Wave JS (core)
Keymaster version
0.5.1
Home Assistant installation type
Home Assistant Container (Docker)
Lock provider
schlage
Lock make and model
Schlage BE468
Steps to reproduce
Symptom — reloading the keymaster config entry (Settings → Devices & Services → keymaster → Reload) crashes during unload and leaves the entry stuck in UNLOAD_IN_PROGRESS/SETUP_IN_PROGRESS; all of the lock's entities go unavailable and only a full HA restart recovers them:
Expected behavior
no stuck entry
Logs
# Reloading the keymaster entry crashes during unload:
2026-06-22 12:03:32.314 ERROR (MainThread) [homeassistant.config_entries] Error unloading entry Side Door for keymaster
Traceback (most recent call last):
File "/config/custom_components/keymaster/__init__.py", line 258, in async_unload_entry
await kmlock.provider.async_unload()
File "/config/custom_components/keymaster/providers/_base.py", line 181, in async_unload
unsub()
ValueError: list.remove(x): x not in list
# Companion errors fired at the same instant (the full lines dump the entire
# KeymasterLock object, so truncated here):
2026-06-22 12:03:32.148 ERROR (MainThread) [homeassistant.core] Unable to remove unknown job listener (<Job onetime listen homeassistant_started functools.partial(<bound method KeymasterCoordinator._create_listeners ...>)) ...[truncated]
2026-06-22 12:03:32.314 ERROR (MainThread) [homeassistant.core] Unable to remove unknown job listener (<Job listen zwave_js_notification ... ZWaveJSLockProvider.subscribe_lock_events ...>) ...[truncated]
# After this, the entry is stuck (UNLOAD_IN_PROGRESS / SETUP_IN_PROGRESS) and all of
# the lock's entities go `unavailable` until a full Home Assistant restart.
Two small notes:
For Issue 1, the line "grepping the entire log set for ZWaveJSProvider returns zero matches" is the strongest single piece of evidence — it directly shows the failure path logs nothing. I confirmed it across all your log rotations (count = 0).
For Issue 2, I filled in unsub() as the line-181 source from the integration's providers/_base.py (the for unsub in self._listeners: unsub() loop) so the traceback is complete and points the maintainer straight at the fix.
Screenshots
No response
Additional context
No response
Describe the bug
NOTE: This was generated by Claude after resolving issues with keymaster on my environment (2 of 2 issues identified)
Environment
keymaster v0.5.1 (HACS)
Home Assistant 2026.5.4, Z-Wave JS (core)
Lock: Allegion/Schlage BE468 (ZWaveJS provider)
Symptom — reloading the keymaster config entry (Settings → Devices & Services → keymaster → Reload) crashes during unload and leaves the entry stuck in UNLOAD_IN_PROGRESS/SETUP_IN_PROGRESS; all of the lock's entities go unavailable and only a full HA restart recovers them:
ERROR [homeassistant.core] Unable to remove unknown job listener (<Job onetime listen homeassistant_started ... KeymasterCoordinator._create_listeners ...>)
ERROR [homeassistant.config_entries] Error unloading entry Side Door for keymaster
Traceback (most recent call last):
File ".../custom_components/keymaster/init.py", line 258, in async_unload_entry
await kmlock.provider.async_unload()
File ".../custom_components/keymaster/providers/_base.py", line 181, in async_unload
ValueError: list.remove(x): x not in list
Analysis — BaseLockProvider.async_unload() (providers/_base.py) unconditionally calls every stored unsub:
async def async_unload(self) -> None:
# Unsubscribe all listeners
for unsub in self._listeners:
unsub() # line 181
self._listeners.clear()
One of the unsub() callbacks performs a list.remove() on a listener that has already been removed (double-unsubscribe, or it was never registered), raising ValueError. This is the well-known HA listener-lifecycle pattern (cf. home-assistant/core#95823, frenck/spook#760). Because it raises mid-loop, the remaining listeners aren't cleaned up and the entry fails to unload.
Repro: reload the keymaster integration (no special state required).
Suggested fixes
Guard the unsub loop so one bad unsub can't abort cleanup:
for unsub in self.listeners:
try:
unsub()
except ValueError:
pass
self.listeners.clear()
And/or make listener registration/removal idempotent so each unsub is invoked exactly once (and isn't tracked after it's been called).
Impact: this also blocks recovery from other provider issues — the normal "reload to fix" path is unavailable, forcing a full restart. Discovered while trying to reload to recover from companion issue #_.
Home Assistant version
Home Assistant 2026.5.4, Z-Wave JS (core)
Keymaster version
0.5.1
Home Assistant installation type
Home Assistant Container (Docker)
Lock provider
schlage
Lock make and model
Schlage BE468
Steps to reproduce
Symptom — reloading the keymaster config entry (Settings → Devices & Services → keymaster → Reload) crashes during unload and leaves the entry stuck in UNLOAD_IN_PROGRESS/SETUP_IN_PROGRESS; all of the lock's entities go unavailable and only a full HA restart recovers them:
Expected behavior
no stuck entry
Logs
Screenshots
No response
Additional context
No response