Skip to content

fix(matter): fall back to slot-only userName when lock rejects canonical tag - #1246

Merged
raman325 merged 1 commit into
mainfrom
fix/matter-slot-only-fallback-on-invalid-name
Jun 11, 2026
Merged

fix(matter): fall back to slot-only userName when lock rejects canonical tag#1246
raman325 merged 1 commit into
mainfrom
fix/matter-slot-only-fallback-on-invalid-name

Conversation

@raman325

Copy link
Copy Markdown
Owner

Proposed change

Production regression on a real Matter lock (`lock.raman_office`): even after the 4.0.1 `MatterError` catch (#1245), `set_lock_user` keeps failing with `InteractionModelError: InvalidCommand (0x85)`. The lock firmware silently restricts `userName` to alphanumeric characters and rejects the colons in the canonical `lcm::` tag.

Matter spec position: 1.4 Application Cluster spec section 5.2.4.36 SetUser command defines `UserName` as type `string` (UTF-8, max 10 chars) with no explicit charset restriction. The restriction is enforced by individual lock firmwares (Yale, Aqara, etc. have been documented to do this).

Without the fallback: 4.0.1's `MatterError → LockOperationFailed` mapping routes through retry → circuit breaker → `slot_suspended_*` repair issue. The lock keeps rejecting the same name forever; no PIN ever gets written.

Fix

On `MatterError` from `set_lock_user`, retry once with the slot-only fallback (`str(slot)`). The tolerant parser added in #1238 already recognizes digit-only names as the slot binding (originally for length-constrained locks); this just exercises the same path for charset-constrained ones. `parse_tag("1")` returns `(1, "")` — the slot identity survives subsequent reads.

Applied to both paths:

  • UPDATE (rename): canonical → on `MatterError` → slot-only → on second `MatterError` → tolerate (the user record at `existing_user_index` is still valid; only the rename was lost; the credential write proceeds).
  • CREATE (allocate): canonical → on `MatterError` → slot-only → on second `MatterError` → raise `LockOperationFailed` (we need an allocated `user_index` for the credential write).

Cross-lock divergence

raman asked: if lock A in the config entry accepts `lcm:1:Raman` and lock B falls back to `1`, is that handled correctly?

Yes. Each provider runs its own find-or-create-by-tag against its own lock independently. Both lock-side names project to LCM slot 1 via the tolerant parser:

Subsequent reads of either lock find the right user via `_find_user_index_for_slot(1)`. The only visible divergence is the lock's own keypad/firmware display (lock A shows "Raman", lock B shows "1"). LCM's own UI is unaffected because slot display names come from slot config, not the lock-side userName.

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

Test plan

  • Full suite passes locally (1160/1160)
  • New `test_set_user_create_falls_back_to_slot_only_on_charset_rejection`: canonical fails, slot-only succeeds, allocated user_index returned
  • New `test_set_user_create_raises_when_both_attempts_fail`: both attempts fail → `LockOperationFailed`
  • New `test_set_user_update_falls_back_to_slot_only_on_charset_rejection`: canonical fails, slot-only succeeds on rename
  • New `test_set_user_update_tolerates_when_both_attempts_fail`: both fail → tolerated, user_index still returned
  • Existing `test_set_user_update_tolerates_*` tests now exercise the "both attempts fail" branch with their original error types
  • Live Matter lock (raman_office): confirm slot 1 PIN gets written and slot_suspended stops firing

🤖 Generated with Claude Code

Production regression on raman.office's Matter lock: even with the
4.0.1 MatterError catch, set_lock_user keeps failing with
``InteractionModelError: InvalidCommand (0x85)`` because the lock's
firmware restricts userName to alphanumeric characters and rejects
the colons in the canonical ``lcm:<slot>:`` tag. The Matter spec
itself (1.4, DoorLock 5.2.4.36) does not restrict userName charset,
but many lock firmwares add this constraint silently.

Without the fallback, 4.0.1 routes the error through
LockOperationFailed -> retry -> circuit breaker -> slot_suspended.
The lock keeps rejecting the same name, the breaker keeps tripping,
no PIN ever gets written.

Fix: on MatterError, retry once with the slot-only fallback
``str(slot)``. The tolerant parser added in #1238 already recognizes
digit-only names as the slot binding (originally for length-constrained
locks); this just exercises the same path for charset-constrained
ones. The slot identity survives subsequent reads because
parse_tag("1") returns (1, "").

Per-lock divergence is handled correctly. Each provider runs its own
find-or-create-by-tag against its own lock independently. If lock A
accepts ``lcm:1:Raman`` and lock B falls back to ``1``, both still
project to LCM slot 1 via the tolerant parser. The only visible
divergence is the lock's own keypad display (firmware-side); LCM's
own UI is unaffected because slot display names come from slot
config, not the lock-side name.

Applied to both UPDATE (rename) and CREATE (allocate) paths:

* UPDATE: try canonical -> on MatterError, try slot-only -> on second
  failure, tolerate (the user record is still valid; only the rename
  was lost; subsequent credential write proceeds against the
  existing user_index).

* CREATE: try canonical -> on MatterError, try slot-only -> on
  second failure, raise LockOperationFailed (we need an allocated
  user_index for the credential write).

Regression tests cover the four cases: fallback succeeds on UPDATE,
fallback succeeds on CREATE, both fail on UPDATE (tolerated),
both fail on CREATE (LockOperationFailed). The existing
test_set_user_update_tolerates_* tests now exercise the "both attempts
fail" branch with their original error type, with assertions updated
from assert_called_once() to call_count == 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 11, 2026 04:15
@github-actions github-actions Bot added python Pull requests that update Python code bug Something isn't working labels Jun 11, 2026
@raman325
raman325 merged commit 0ae0aec into main Jun 11, 2026
14 checks passed
@raman325
raman325 deleted the fix/matter-slot-only-fallback-on-invalid-name branch June 11, 2026 04:17
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.99%. Comparing base (1c5cdad) to head (315e0ee).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1246   +/-   ##
=======================================
  Coverage   96.98%   96.99%           
=======================================
  Files          53       53           
  Lines        6171     6181   +10     
  Branches      461      461           
=======================================
+ Hits         5985     5995   +10     
  Misses        186      186           
Flag Coverage Δ
python 97.53% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
...m_components/lock_code_manager/providers/matter.py 99.37% <100.00%> (+0.01%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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 addresses a production regression in the Matter provider where some lock firmwares reject the canonical lcm:<slot>: username tag due to non-alphanumeric character restrictions, causing set_lock_user to fail repeatedly and preventing PIN writes. The change introduces a one-time retry using a slot-only username (str(slot)) so the tolerant tag parser can still recover the slot identity on subsequent reads.

Changes:

  • Matter provider: on set_lock_user failure, retry once with slot-only username for both UPDATE (tolerate if both fail) and CREATE (raise LockOperationFailed only after both fail).
  • Tests: add new CREATE/UPDATE fallback scenarios and update existing tolerance tests to reflect the new two-attempt behavior.
  • Logging: add diagnostic logs around canonical rejection and fallback behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
custom_components/lock_code_manager/providers/matter.py Adds slot-only fallback retry for set_lock_user on UPDATE and CREATE paths.
tests/providers/matter/test_provider.py Adds/updates tests to cover fallback success and both-attempts-fail behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +451 to +468
slot_only_name = str(slot)
try:
result = await set_lock_user(
client,
node,
user_index=None,
user_name=slot_only_name,
)
except MatterError as fallback_err:
# Second attempt also failed -- the lock is rejecting more
# than just the charset. Surface as LockOperationFailed so
# the seam routes through retry rather than the catchall
# suspend path (which would create a spurious repair issue).
raise LockOperationFailed(
f"Matter set_lock_user failed for {self.lock.entity_id} "
f"on both canonical and slot-only fallback names: "
f"canonical={err}, slot-only={fallback_err}"
) from fallback_err
Comment on lines +2399 to +2401
user = User(user_id=1, name="lcm:1:Alice")
with (
patch.object(
Comment on lines +2471 to +2473
user = User(user_id=2, name="lcm:2:Updated Name")
with (
patch.object(
Comment on lines +414 to +417
LOGGER.info(
"Lock %s: lock rejected canonical tag %r for slot %s "
"(%s); renamed to slot-only %r so the slot binding "
"survives the read",
raman325 added a commit that referenced this pull request Jun 11, 2026
… caplog level (#1248)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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