Skip to content

fix(pin): hold garage covers by device class and through groups (#773) - #792

Merged
goruck merged 6 commits into
mainfrom
fix/cover-group-device-class-pin
Oct 18, 2026
Merged

goruck merged 6 commits into
mainfrom
fix/cover-group-device-class-pin

Conversation

@goruck

@goruck goruck commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes #773. The recommended Critical Action rules match door, gate or garage against the cover's entity id only, so a cover with a plain id (cover.msg100_abc) opened with no PIN even when its device class or name said garage, and so did a cover group like cover.all_covers holding the garage door, directly, by name, in a scene, or in an automation the agent wrote.

Fix

New agent/rule_entities.py: expand_critical_rules(hass, rules) builds one index of states, the entity registry and the device registry, and for each entity_match rule adds a copy matching one entity exactly (internal _exact_entity key, stripped from stored rules; read through one shared rule_entity_matches predicate in matches_critical_rule and _entity_is_guarded) for each entity in the rule's domain that:

  • has the word as its device class;
  • has the word or its plural, with no letter next to it, in its friendly, registry, integration, alias or device name, unless its device class is a window covering;
  • is a group (state entity_id, MQTT/ZHA group_entities, group helper config entry) holding a match, at any depth;
  • is not set up yet with no device class (no state or a restored placeholder; not disabled), a group member HA does not know, or a group helper whose members cannot be read: held, because an automation runs later.

Applied in the tool-call gate (_critical_action_guard), add_automation, and the audit's webhook-automation check. A failure in the extension fails closed at the gate; the audit falls back to the configured rules.

Known gaps (documented)

Review

gstack review, 3 cycles (specialists, Claude adversarial, Codex, red team each cycle); not converged after cycle 3's fixes, which were not re-reviewed.

Release

A safety gate not operating, so per RELEASING.md this ships alone as a patch.

Tests

test_cover_group_pin_gate.py (59). make lint, make test (5222 passed), make typecheck clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SFM2hXVUELSh13U7FEKGJu

The recommended cover rules matched door/gate/garage in the entity id
only, so a cover with device class garage and a plain id, or a cover
group holding the garage door, opened with no PIN on every path.

expand_critical_rules adds, per entity_match rule, a copy naming each
entity whose device class contains the word and each group (cover group
or old-style group, any depth) holding a matching entity. The tool-call
gate and add_automation extend their rules before screening, so intents,
scripts, scenes and automation contents all see the same reach.

Fixes #773

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFM2hXVUELSh13U7FEKGJu
@claude

claude Bot commented Oct 18, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Review of the first fix found it saw only loaded entities:
- a registered garage cover not loaded yet, an unavailable cover group
  (no member list) and MQTT/ZHA groups (group_entities) were missed, so
  add_automation could install an automation that opens the garage later;
- a relay cover named "Garage Door" with no device class still ran.

The rule word now matches the device class exactly, or a whole word in
the friendly name, registry name or alias; the index reads the entity
registry, group_entities and group helper config entries, holds a group
helper whose members cannot be read, and walks groups through a
member-to-group index in one pass. The security audit's webhook
automation check uses the same extended rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFM2hXVUELSh13U7FEKGJu
goruck and others added 2 commits October 17, 2026 18:00
Second review cycle:
- a rule word now matches its plural and is read with `_`/`-` as spaces,
  so "Driveway Gates" and a rule written as `garage_door` match names;
- an unloaded entity is known by its device's name too;
- a group member not yet loaded or registered is matched by its id, and
  an unloaded entity in the rule's domain with no device class is held
  (it could be an MQTT/ZHA group whose members are unseen);
- an id seeds groups only when the word starts it or a part of it, so a
  group of outdoor awnings is not held for "door";
- CONF_ENTITIES, docstrings, docs and the audit's stale "mirroring" note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFM2hXVUELSh13U7FEKGJu
Third review cycle:
- each rule copy for one entity matches that id exactly through an
  internal marker (stripped from stored rules) and one shared predicate,
  so a held leftover cover.living_room no longer holds its blinds and
  cover.a no longer holds cover.awning; the copy keeps the user's word
  so a reader unaware of the marker still sees the original rule;
- restored startup placeholders count as not set up; disabled entities
  are never held; a group member HA does not know holds its groups;
- names match the word with no letter next to it ("Gate1"), and a
  window-covering device class is not matched by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFM2hXVUELSh13U7FEKGJu
goruck and others added 2 commits October 18, 2026 00:29
…at a call holds (#773)

Re-review fixes (gstack review, 3 cycles):
- Device class matches against every class an entity has (state, user
  "Show as", integration's original); only the integration's own class can
  mark a window covering and turn off the name match. Unregistered entities
  read it from the loaded entity, so YAML `customize` does not count.
- An entity not loaded is held unless its integration classes it as a
  window covering (an unloaded MQTT/ZHA group classed shutter is held).
- One entity that raises while being read is logged and held instead of
  failing every guarded call; a warning when there is no hass.
- Group members match by the word anywhere in the id, as direct calls do,
  so a group holding cover.backdoor is held like the cover.
- The network audit extends rules only when an automation has a public
  webhook.
- Tests: Show-as and customize, unloaded classed group, unreadable entity
  (held and logged), no-hass warning, script through the group,
  add_automation fail-closed, the audit's lazy path with a positive control,
  the collector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SFM2hXVUELSh13U7FEKGJu
@goruck
goruck merged commit f9b390b into main Oct 18, 2026
11 checks passed
@goruck
goruck deleted the fix/cover-group-device-class-pin branch October 18, 2026 12:01
goruck added a commit that referenced this pull request Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cover groups, and garage/gate covers whose entity id lacks door/gate/garage, are not held for the Critical Action PIN

1 participant