Skip to content

Attribute cache restore from zigbee.db clears unsupported-attribute marks declared by quirks #1875

Description

@markoweb

Describe the bug

AttributeCache.set_value() unconditionally removes the attribute's unsupported marker before writing the value (zigpy/zcl/helpers.py):

def set_value(self, attr_def, value, *, last_updated=None):
    self.remove_unsupported(attr_def)
    ...

The zigbee.db restore path writes every persisted attribute row back through this method at startup (zigpy/appdb.py, cluster._attr_cache.set_value(...)).

Together these mean a quirk that declares an attribute unsupported at cluster instantiation — the documented add_unsupported_attribute() pattern, used by many quirks to tell consumers "this device will never provide this attribute" — has that declaration silently erased on every startup whenever a stale cached value for the attribute exists in the database. Stale values are easy to acquire: any earlier quirk version (or the bare cluster) that once populated the attribute leaves rows behind, and quirk swaps don't purge them.

Clearing the marker on set_value() is sound semantics for live traffic — a successful read or report is genuine evidence the device supports the attribute. The database restore is not new evidence, though: it just replays history, possibly from a different quirk's era, and it happens after the current quirk's clusters have been instantiated and have made their declarations.

Impact (downstream, zha)

zha's entity discovery gates sensor creation on Cluster.is_attribute_unsupported(). With the mark wiped by the restore, zha creates entities for attributes the active quirk explicitly declared unavailable. Nothing ever feeds them, so users get permanently-"Unknown" entities that cannot be deleted (the integration keeps providing them) and reappear after every restart.

Concrete case: the Tuya PJ-1203A quirk in zigpy/zha-device-handlers#4498 marks Metering.instantaneous_demand and several ElectricalMeasurement attributes unsupported. Users migrating from an earlier custom quirk that populated those attributes get zombie "Instantaneous demand" / "Power factor" sensors on every start (HA 2026.8.1: zigpy 2.1.0 / zha 2.1.0 / zha-quirks 2.2.0). Reproduced end-to-end with the zha test harness: a fresh join is clean; the same join with pre-seeded cache values recreates every zombie.

Minimal reproduction (pure zigpy 2.1.0)

from unittest.mock import MagicMock
from zigpy.zcl.clusters.smartenergy import Metering

cluster = Metering(MagicMock())
attr = Metering.AttributeDefs.instantaneous_demand

cluster.add_unsupported_attribute(attr.id)
assert cluster.is_attribute_unsupported(attr.name)  # True — quirk's declaration

# What the zigbee.db attribute restore does on startup (zigpy/appdb.py):
cluster._attr_cache.set_value(attr, 1234)
assert not cluster.is_attribute_unsupported(attr.name)  # mark silently erased

Expected behavior

An unsupported mark set programmatically on the current cluster instance (i.e. by the active quirk) should survive the persistence restore. Values written by live reads/reports clearing the mark is fine.

Possible directions

  • Give the restore path a way to write values without clearing marks (e.g. set_value(..., clear_unsupported=False) used by appdb), or
  • Skip restoring cached values for attributes currently marked unsupported on the instantiated (quirked) cluster, or
  • Re-assert quirk-declared marks after the restore completes.

Any of these would also stop the stale rows from being re-persisted indefinitely.

Workaround

Quirk-side, on zha-quirks 2.x: suppress the affected default entities with prevent_default_entity_creation(...) (proposed for the PJ-1203A quirk in zigpy/zha-device-handlers#4498). This hides the symptom but doesn't restore the add_unsupported_attribute() contract for other quirks.

Environment

  • zigpy 2.1.0 (Home Assistant 2026.8.1, zha 2.1.0, zha-quirks 2.2.0)
  • Radio: n/a (persistence-layer behavior, reproducible without a radio)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions