Orphaned automation registry rows: find by declaration site, remove by entity_id
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
- Verification script overwrote its own input. The detection script wrote the
orphan list to
/tmp/ha-orphan-list.jsonand 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. - Recursive sweep must recurse into surviving containers. A card-removal pass
that skipped
cards/sectionskeys 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-*.