Skip to content

[Core] Moving an entity to another device keeps a stale disabled_by: device #81

Closed

Description

@zigpy-review-bot

Problem

When an entity registry entry moves to another device, the entity registry does not re-evaluate a disabled_by: device disabler. An entity that was disabled together with its old device stays disabled_by: device on the new device, even when the new device is enabled.

Nothing clears it later. EntityRegistry.async_device_modified only re-enables device-disabled entities when the device they are on now gets a registry update, and the frontend locks the "Enabled" switch for disabled_by: device (see #79). So the entity stays disabled for no visible reason, until the user happens to change the new device (rename it, change its area, or disable and re-enable it).

Both ways of moving an entity are affected:

  • The entity platform, when an entity is added with a device_info that resolves to another device: EntityRegistry.async_get_or_create updates device_id of the existing entry and keeps its disabled_by. The platform only handles the opposite direction: an enabled entity added to a disabled device gets disabled_by: device (entity_platform.py L1146).
  • EntityRegistry.async_update_entity(entity_id, device_id=...) handles neither direction (_async_update_entity only adjusts config_entry disablers).

Why it matters now

Child devices (home-assistant/architecture#1414) make moving entities between devices routine: an integration moves them between the main device and its child devices whenever the split changes (a re-interview, a quirk or firmware change, a device losing an endpoint). If the user had disabled a child device, its entities come back on the main device disabled. ZHA (zigpy/zha#947), Z-Wave (home-assistant/core#185985) and Matter (home-assistant/core#186033) all move entities this way. The same stuck disabler showed up while reviewing the Matter PR: after a child device was pruned and the entry reloaded, its outlet switch came back on the enabled node device with disabled_by: device.

Reproduction

On the ZHA child device branch (TheJulianJES/core zigpy-bot/zha-child-devices at TheJulianJES/core@ce12ea0a44a, with zigpy/zha#947 at zigpy/zha@a3bd6ff5), with ZHA's own fix-up (_async_reconcile_child_devices) patched out:

  1. Join a two-socket Tuya TS011F, which gets the child devices "Channel 1" and "Channel 2".
  2. Disable the "Channel 1" child device. Its switch becomes disabled_by: device.
  3. Re-interview the device with the split turned off. The entity platform moves the switch back to the main device, which is enabled, but the switch stays disabled_by: device.

Expected

When an entity's device_id changes, the entity registry reconciles a device disabler with the new device: clear it if the new device is enabled, and set it if the new device is disabled. This matches what already happens to config_entry disablers when an entity's config entry changes.

Workaround

The ZHA branch fixes the disabler by hand after every move (_async_move_entity in homeassistant/components/zha/helpers.py). Clearing it re-enables the entity, so EntityRegistryDisabledHandler reloads the ZHA config entry 30 seconds later, as for any re-enabled entity.

Related

Activity

  1. added
    HA coreNeeds (or is caused by) a change in HA Core or the HA frontend
    on Oct 19, 2026
  2. zigpy-review-bot commented on Oct 20, 2026

    @zigpy-review-bot
    CollaboratorAuthor

    Also fixed by home-assistant/core#186750: _async_update_entity now derives a device / config_entry / unset disabled_by again whenever device_id or config_entry_id changes, in both directions. This covers the entity platform path (moving an existing entry in async_get_or_create goes through it) as well as direct async_update_entity(device_id=...) calls, and the platform's one-way special case is removed. Covered by test_update_entity_device_id_disabled_by.

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

    HA coreNeeds (or is caused by) a change in HA Core or the HA frontend

    Type

    Fields

    Priority

    None yet

    Size

    None yet

    Area

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions