Skip to content

Commit 40c7255

Browse files
docs(#1157): bump skills-vendor + auto-update via Renovate + native for: field + scrub eval_template anti-patterns (#1383)
* chore: bump skills-vendor to cf2b714 (HA 2026.5 best-practices) Brings in: - 'Purpose-Specific Triggers (2026.2+)' section in automation-patterns.md (door/window/motion/temperature triggers backed by standard state/numeric_state) - HA 2024+ plural-keyword form (triggers:/actions:/conditions:) across YAML examples - 2026.4+ template helpers documented (entity_name, state_attr_translated) - New yaml-only-integrations.md reference - Continue-on-Error, Repeat Actions, Disabling Automations sections Refs #1157. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(automations): teach native `for:` field in ha_config_set_automation Two changes to the ha_config_set_automation docstring: 1. Add a 'for:' bullet to the PREFER NATIVE SOLUTIONS list, pointing at the native for: field on state/numeric_state triggers and conditions as the replacement for {{ now() - X.last_changed > timedelta(...) }} duration math. Notes that HA 2026.5 expanded for: to purpose-specific triggers (motion, occupancy, doors, windows). 2. Modernize the Motion Light example. The previous example taught the stale turn_on + delay + turn_off pattern with mode: restart. Replace with a two-trigger + choose pattern using motion-on / motion-off (with for: 5 minutes) and native trigger-id conditions — no templates, no action-delay, mode: single suffices because for: handles re-trigger semantics. Refs #1157. Complements the best-practice detector in #1264. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(eval_template): remove automation-logic template examples ha_eval_template's docstring was teaching the exact anti-patterns the best-practice checker now flags. The 'Automation Conditions' use-case block showed `{{ is_state(...) and now().hour >= 7 }}` as the example of how to do conditions, and 'Test complex automation condition' wrote out the same shape combined with a `| float > 20` numeric comparison. Both teach agents that template logic is how you build automation conditions; the project actively steers the opposite direction. Replace with: - A 'When NOT to use this for automation/script logic' callout listing the native alternatives (numeric_state, state, time, sun, for:) and pointing at ha_get_skill_guide for the full list. - Reframe remaining examples so they're shown as legitimate template positions (notification bodies, dynamic data.* values, debugging existing templates) rather than as logic-building examples. Refs #1157. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(renovate): enable git-submodules manager for skills-vendor The skills bundle (`src/ha_mcp/resources/skills-vendor`) is a git submodule. Submodules are pinned by design, and the workflows checkout with `submodules: true` — meaning each release ships exactly the SHA pinned at build time. Without auto-update infra, the pinned SHA drifts silently behind upstream until someone manually bumps it; that 'silently' is the failure mode that led to this PR (helper-selection.md served at runtime was missing the `filter`, `random`, `generic_thermostat`, `generic_hygrostat`, and `switch_as_x` helpers — all real types the `ha_config_set_helper` tool advertises). Enable Renovate's `git-submodules` manager so the submodule pointer gets opened as a reviewable PR whenever upstream main advances. The existing global Tuesday-3pm-UTC schedule applies. `.gitmodules` needs `branch = main` for Renovate to know which upstream ref to track — without it, Renovate defaults to `master`, which doesn't exist on `homeassistant-ai/skills`. The next time `homeassistant-ai/skills` lands a commit on `main`, Renovate will open a bump PR; merging it ships the new skill content in the next `publish-dev`/release cycle. Refs #1157. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: address review — drop unverified version claim, fix Motion Light logic hole Three fixes from the contrib + Gemini review on this branch: 1. Drop the parenthetical claim that 'HA 2026.5 expanded for: to purpose-specific triggers'. The claim conflates two distinct things: for: on state/numeric_state has existed for years; the purpose- specific triggers (introduced 2025.12 / labelled 2026.2+ in the vendored skill) are a UI editor abstraction over the same YAML and are still a Labs preview behind an opt-in flag. The bullet's main point — use native for: instead of last_changed/last_updated math — stands on its own. 2. Motion Light example: top-level 'condition: sun, after: sunset' gated both the motion_on and motion_off branches of the choose. If the sun rose during the 5-minute for: window, motion_off fired but the top-level condition was false, leaving the light on. Move the sun condition into the motion_on branch only (matches Gemini's suggestion). Rephrase the example header to describe the actual change ('for: on the off-transition replaces action-delay') instead of overclaiming 'no template' (the previous example was also template-free). 3. ha_eval_template docstring: the Conditional Logic block showed '{% if is_state('sun.sun', 'above_horizon') %}' as a Jinja2 syntax example — but the new 'When NOT to use' callout explicitly tells agents not to write 'is_state('sun.sun', ...)' in condition positions. Swap the example subject to alarm_control_panel state (no native equivalent for the notification-string use case it illustrates) and tighten the section heading to 'Conditional Logic (for display strings — not for condition: positions)'. Also add a 'choose action over templated service: / action: strings' bullet to the callout — the lead-in mentioned action service names but no bullet covered them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e0e59ee commit 40c7255

5 files changed

Lines changed: 42 additions & 30 deletions

File tree

.gitmodules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[submodule "src/ha_mcp/resources/skills-vendor"]
22
path = src/ha_mcp/resources/skills-vendor
33
url = https://github.com/homeassistant-ai/skills.git
4+
branch = main

renovate.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
],
1010
"enabledManagers": [
1111
"custom.regex",
12-
"dockerfile"
12+
"dockerfile",
13+
"git-submodules"
1314
],
1415
"ignorePaths": [],
1516
"customManagers": [

src/ha_mcp/tools/tools_config_automations.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,8 @@ async def ha_config_set_automation(
457457
`{{ states(x) in [...] }}`
458458
- `condition: time` instead of `{{ now().hour ... }}` or `{{ now().weekday() ... }}`
459459
- `condition: sun` instead of `{{ is_state('sun.sun', ...) }}`
460+
- Native `for:` field on `state`/`numeric_state` triggers and conditions over
461+
`{{ now() - X.last_changed > timedelta(...) }}` duration math.
460462
- `wait_for_trigger` instead of `wait_template`
461463
- `choose` action instead of template-based service names
462464
- For one-shot date firing, use a `time` trigger plus `automation.turn_off` on a
@@ -518,17 +520,25 @@ async def ha_config_set_automation(
518520
"action": [{"service": "light.turn_on", "target": {"area_id": "bedroom"}}]
519521
})
520522
521-
Motion-activated lighting with condition:
523+
Motion-activated lighting — `for:` on the off-transition replaces action-delay:
522524
ha_config_set_automation(config={
523525
"alias": "Motion Light",
524-
"trigger": [{"platform": "state", "entity_id": "binary_sensor.motion", "to": "on"}],
525-
"condition": [{"condition": "sun", "after": "sunset"}],
526-
"action": [
527-
{"service": "light.turn_on", "target": {"entity_id": "light.hallway"}},
528-
{"delay": {"minutes": 5}},
529-
{"service": "light.turn_off", "target": {"entity_id": "light.hallway"}}
526+
"trigger": [
527+
{"platform": "state", "entity_id": "binary_sensor.motion", "to": "on", "id": "motion_on"},
528+
{"platform": "state", "entity_id": "binary_sensor.motion", "to": "off",
529+
"for": {"minutes": 5}, "id": "motion_off"}
530530
],
531-
"mode": "restart"
531+
"action": [
532+
{"choose": [
533+
{"conditions": [
534+
{"condition": "trigger", "id": "motion_on"},
535+
{"condition": "sun", "after": "sunset"}
536+
],
537+
"sequence": [{"service": "light.turn_on", "target": {"entity_id": "light.hallway"}}]},
538+
{"conditions": [{"condition": "trigger", "id": "motion_off"}],
539+
"sequence": [{"service": "light.turn_off", "target": {"entity_id": "light.hallway"}}]}
540+
]}
541+
]
532542
})
533543
534544
Update existing automation:

src/ha_mcp/tools/tools_utility.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,13 +1026,13 @@ async def ha_eval_template(
10261026
{{ now().weekday() }} # Day of week (0=Monday)
10271027
```
10281028
1029-
**Conditional Logic:**
1029+
**Conditional Logic (for display strings — not for `condition:` positions):**
10301030
```jinja2
10311031
{{ 'Day' if now().hour < 18 else 'Night' }} # Ternary operator
1032-
{% if is_state('sun.sun', 'above_horizon') %}
1033-
It's daytime
1032+
{% if is_state('alarm_control_panel.home', 'armed_away') %}
1033+
Alarm is armed
10341034
{% else %}
1035-
It's nighttime
1035+
Alarm is disarmed
10361036
{% endif %}
10371037
```
10381038
@@ -1059,16 +1059,21 @@ async def ha_eval_template(
10591059
{{ device_id('light.bedroom') }} # Get device ID for entity
10601060
```
10611061
1062-
**Common Use Cases:**
1063-
1064-
**Automation Conditions:**
1065-
```jinja2
1066-
# Check if it's a workday and after 7 AM
1067-
{{ is_state('binary_sensor.workday', 'on') and now().hour >= 7 }}
1068-
1069-
# Temperature-based condition
1070-
{{ states('sensor.outdoor_temp') | float < 0 }}
1071-
```
1062+
**When NOT to use this for automation/script logic:**
1063+
Templates have legitimate uses (notification bodies, dynamic `data.*` values,
1064+
debugging existing templates), but `condition:` / `trigger:` positions and
1065+
action service names are better expressed as native HA constructs — they
1066+
validate at config load, fail loudly, and avoid silent runtime failures.
1067+
Prefer:
1068+
- `condition: numeric_state` over `{{ states('x') | float > N }}`
1069+
- `condition: state` over `{{ is_state(...) }}`
1070+
- `condition: time` / `condition: sun` over `now().hour` / `is_state('sun.sun', ...)`
1071+
- Native `for:` field on state/numeric_state triggers and conditions over
1072+
`{{ now() - X.last_changed > timedelta(...) }}` duration math
1073+
- `choose` action over templated `service:` / `action:` strings
1074+
See `ha_get_skill_guide` (best-practices skill) for the full anti-pattern list.
1075+
1076+
**Common Use Cases (legitimate template positions):**
10721077
10731078
**Dynamic Service Data:**
10741079
```jinja2
@@ -1086,7 +1091,7 @@ async def ha_eval_template(
10861091
ha_eval_template("{{ states('light.living_room') }}")
10871092
```
10881093
1089-
**Test conditional logic:**
1094+
**Test a string expression (e.g. for a notification body):**
10901095
```python
10911096
ha_eval_template("{{ 'Day' if now().hour < 18 else 'Night' }}")
10921097
```
@@ -1096,11 +1101,6 @@ async def ha_eval_template(
10961101
ha_eval_template("{{ (states('sensor.temperature') | float + 5) | round(1) }}")
10971102
```
10981103
1099-
**Test complex automation condition:**
1100-
```python
1101-
ha_eval_template("{{ is_state('binary_sensor.workday', 'on') and now().hour >= 7 and states('sensor.temperature') | float > 20 }}")
1102-
```
1103-
11041104
**Test entity counting:**
11051105
```python
11061106
ha_eval_template("{{ states.light | selectattr('state', 'eq', 'on') | list | count }}")

0 commit comments

Comments
 (0)