Skip to main content

Orphaned automation registry rows: find by declaration site, remove by entity_id

entry

Date: 2026-09-20 Lane: hermes Source: observed on Zappo (macbook-pro-3) while clearing HA unavailable-entity noise

Why the rows existed​

18 cast automations were removed from automations.yaml and packages/roof_sun.yaml on 2026-09-15 during the manual-controls conversion. Their entity-registry rows persisted, so Home Assistant kept restoring them as unavailable entities. This inflated HA's unavailable count and left dead cards on two dashboards.

The converted logic was not lost: packages/manual_controls.yaml holds all 18 replacement scripts. Verify that before removing any registry row.

Detection: count declaration sites, not just automations.yaml​

configuration.yaml declares automations in TWO forms:

  • automation: !include automations.yaml
  • inline automation <name>: blocks (note the SPACE in the key)

A scanner that only reads lists misses the inline form and falsely reports the live sqlite_journal_mode_bootstrap automation as an orphan. It also parses YAML with ! tags only under the HA venv (/Users/dwayne/.homeassistant-venv/bin/python3) with a tolerant SafeLoader (add_multi_constructor('!', lambda l,s,n: None)).

Correct count on this system: 13 defined, 33 registry rows, 20 orphans.

Removal: targeted by entity_id, never Spook's blanket service​

homeassistant.delete_all_orphaned_entities (Spook) deletes EVERY entity carrying ATTR_RESTORED. On this system that is 98 entities, 78 of which are unrelated Roborock, light, camera and tracker entities that must survive. Do not use it.

Remove by websocket instead:

{"type": "config/entity_registry/remove", "entity_id": "<id>"}

Auth handshake on ws://127.0.0.1:8123/api/websocket: read auth_required, send {"type":"auth","access_token":...}, expect auth_ok. Confirm each target with config/entity_registry/get before removing.

Check live dashboards for references first. Remove the registry row, then strip the dead reference from the dashboard (.storage/lovelace.*), then drop any entity card left with an empty entities list.

Two traps hit​

  1. Verification script overwrote its own input. The detection script wrote the orphan list to /tmp/ha-orphan-list.json and was re-run after removal, replacing the list with []. A later cleanup step then found nothing to do. Recover by diffing the pre-change registry backup against the current one. Keep the list and the reporter as separate files.
  2. Recursive sweep must recurse into surviving containers. A card-removal pass that skipped cards/sections keys when walking children missed nested cards, so the empty card survived one round. Recurse into every value.

Result​

Automation entities 33 -> 13, unavailable automations 20 -> 0, all-domain unavailable 126 -> 106. hass --script check_config exit 0. 4 dead dashboard refs and 1 empty card removed. Backups under ~/.hermes/backups/ha-orphan-cleanup-* and ~/.homeassistant/.storage/*.bak-dead-automation-refs-*.