fix(matter): tolerate MatterError on set_lock_user UPDATE; map on CREATE - #1245
Conversation
Production regression observed on a real Matter lock: set_lock_user on an existing user occasionally throws matter_server.common.errors. UnknownError: InteractionModelError: InvalidCommand (0x85). UnknownError is a MatterError subclass, NOT a HomeAssistantError subclass, so the original except HomeAssistantError at matter.py:379 silently let it bubble past. The seam's catchall in _async_tick_impl then routed every failure to _suspend_slot, creating a spurious slot_suspended_* repair issue even though the underlying lock user record was fine and the credential write would have succeeded. The docstring at the catch site already documented the right intent: the rename is metadata-only and the historical contract (PR #1077) tolerates name-set failures. The fix widens the catch to (HomeAssistantError, MatterError) so the contract holds for Matter-SDK-side rejections too. For the CREATE path we cannot tolerate the failure -- without an allocated user_index the subsequent credential write has no target. The fix adds a MatterError -> LockOperationFailed mapping so the seam retries through the normal backoff path rather than the catchall suspend. Regression tests pin both branches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 9c5984fffec0
There was a problem hiding this comment.
Pull request overview
This PR fixes a regression in the Matter provider where Matter-SDK exceptions from set_lock_user were not being handled per the established contract, causing the sync seam to treat transient/benign failures as “unexpected” and incorrectly suspend slots (creating repair issues).
Changes:
- Expand the UPDATE (rename-only) exception handling to also tolerate
MatterErrorso credential writes can still proceed when name updates are rejected by the lock/Matter SDK. - Map
MatterErroron the CREATE path toLockOperationFailedso the seam retries via normal backoff instead of falling into the catchall suspend path. - Add targeted tests covering UPDATE tolerance and CREATE error mapping for Matter-SDK errors (
UnknownError).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
custom_components/lock_code_manager/providers/matter.py |
Broadens exception handling for set_lock_user to preserve the UPDATE tolerance contract and route CREATE failures through retry semantics. |
tests/providers/matter/test_provider.py |
Adds regression tests ensuring Matter-SDK errors are handled correctly on UPDATE vs CREATE. |
💡 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 #1245 +/- ##
=======================================
Coverage 96.98% 96.98%
=======================================
Files 53 53
Lines 6168 6171 +3
Branches 461 461
=======================================
+ Hits 5982 5985 +3
Misses 186 186
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Proposed change
Production regression observed on a real Matter lock (`lock.raman_office`, slot 1): `set_lock_user` on an existing user occasionally throws `matter_server.common.errors.UnknownError: InteractionModelError: InvalidCommand (0x85)`. `UnknownError` is a `MatterError` subclass, not a `HomeAssistantError` subclass, so the original `except HomeAssistantError` at `matter.py:379` silently let it bubble past. The seam's catchall in `_async_tick_impl` then routed every failure to `suspend_slot`, creating a spurious `slot_suspended*` repair issue even though the lock user record was fine and the credential write would have succeeded.
The docstring at the catch site already documented the right intent:
The fix widens the catch to `(HomeAssistantError, MatterError)` so the contract holds for Matter-SDK-side rejections too.
For the CREATE path we cannot tolerate the failure — without an allocated `user_index` the subsequent credential write has no target. The fix adds a `MatterError → LockOperationFailed` mapping so the seam retries through the normal backoff path rather than the catchall suspend (which would still spuriously create a repair issue without giving the lock a chance to recover).
Production traceback
```
ERROR [custom_components.lcm.domain.sync] Raman's Office: lock.raman_office slot 1:
Unexpected error during set usercode. Sync suspended for this lock to prevent
infinite retry loop. Error: UnknownError: InteractionModelError: InvalidCommand (0x85)
File "/config/custom_components/lcm/providers/matter.py", line 373, in async_set_user
await set_lock_user(client, node, user_index=existing_user_index, user_name=user.name)
File "matter_server/client/client.py", line 611, in send_command
return await future
matter_server.common.errors.UnknownError: InteractionModelError: InvalidCommand (0x85)
```
Type of change
Test plan
🤖 Generated with Claude Code