Home Assistant skill for Claude Code

Automations that fire right and explain themselves. Claude writes Home Assistant automations in the post-2024.10 schema with stable IDs, the right mode, debounced triggers and guards against unavailable states. Templates get defaults and availability checks, repeated logic becomes blueprints or scripts with fields, configs are split into packages with secrets kept out of git, and missed automations are diagnosed from their traces.

  • namehome-assistant
  • size259 lines · ≈2,250 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Writes and debugs Home Assistant configuration — automations, scripts, and scenes in current YAML syntax (triggers, conditions, actions), template sensors and helpers, blueprints with inputs, trigger IDs with choose, modes, packages, secrets, and integration setup — and troubleshoots with traces, the template editor, and config checks. Use when creating or fixing a Home Assistant automation, writing Jinja templates for sensors, building a blueprint, organizing configuration.yaml, migrating old service/platform syntax, or figuring out why an automation did not fire.

Try asking

  • Write an automation that turns on the hallway light on motion after sunset and off 2 minutes later
  • My garage door alert automation never fires, here is the YAML, find out why
  • Convert my old service and platform automations to the current Home Assistant syntax

Use it when

  • Creating or fixing Home Assistant automations or scripts
  • Writing template sensors or Jinja templates
  • Building a reusable blueprint
  • Reorganizing a large configuration.yaml

Skip it when

  • You are writing firmware for the sensor device itself — use esp32-firmware
  • You are designing the MQTT topic layout for devices — use mqtt-iot

How to check the result

The configuration check passes, the automation's trace shows every step as intended on a real trigger, and a restart does not fire it from unavailable-to-on transitions.

The common mistake it prevents

Triggering on to: "on" without a from state, so devices recovering from unavailable after a restart fire the automation.

~/.claude/skills/home-assistant/SKILL.md

--- name: home-assistant description: Writes and debugs Home Assistant configuration — automations, scripts, and scenes in current YAML syntax (triggers, conditions, actions),… license: MIT ---

Done well means: automations fire exactly when intended, survive restarts and devices going unavailable, use the current YAML schema, keep secrets in secrets.yaml, pass the configuration check before reload, and can be explained from their trace.

Hard rules

  • Check configuration before every restart or reload (Developer Tools -> YAML -> Check configuration, or ha core check on HA OS). A YAML error can stop integrations from loading.
  • Secrets (API keys, passwords, tokens, lat/long if sharing config) go in secrets.yaml referenced with !secret name; keep it out of git.
  • Locks, garage doors, alarms, heating, and anything safety-related: require explicit user confirmation of the logic, guard against unavailable/unknown, and prefer notifications with actionable confirmation over silent automatic unlocks.
  • Back up before large refactors (Settings -> System -> Backups) and keep /config in git (with a .gitignore for secrets.yaml, .storage/, databases, and logs).

Current syntax (2024.10 and later)

Old configs use platform: inside triggers, service: for actions, and singular trigger:/condition:/action: block keys. Current syntax:

- id: hallway_motion_light            # stable id enables UI editing and traces
  alias: Hallway light on motion
  description: Turn on hallway light at night on motion; off 2 min after motion stops
  mode: restart                       # re-trigger restarts the timer
  triggers:
    - trigger: state
      entity_id: binary_sensor.hallway_motion
      from: "off"
      to: "on"
  conditions:
    - condition: state
      entity_id: sun.sun
      state: below_horizon
  actions:
    - action: light.turn_on
      target:
        entity_id: light.hallway
      data:
        brightness_pct: 60
    - wait_for_trigger:
        - trigger: state
          entity_id: binary_sensor.hallway_motion
          to: "off"
          for: "00:02:00"
    - action: light.turn_off
      target:
        entity_id: light.hallway

Old syntax still loads, but write new code in the current form and convert files you touch.

Workflow

- [ ] 1. State the behavior as "when X, if Y, do Z, unless W"
- [ ] 2. Inspect entities: Developer Tools -> States (exact entity_id, states, attributes)
- [ ] 3. Choose trigger(s), conditions, mode
- [ ] 4. Write YAML (or UI editor, then review YAML); prototype templates in the template editor
- [ ] 5. Check configuration; reload automations (no restart needed)
- [ ] 6. Test: run actions manually, then trigger for real; read the trace
- [ ] 7. Handle edge cases: unavailable, restart, rapid re-triggers

Triggers and modes

NeedTrigger
Device changes statetrigger: state with from/to; add for: to debounce
Numeric thresholdtrigger: numeric_state with above/below (fires on crossing only)
Timetrigger: time (at: "07:00:00" or an input_datetime) / time_pattern
Suntrigger: sun with event: sunset and offset
Presencetrigger: zone or state of person.*
Button / remote eventtrigger: event or device trigger (device triggers use device IDs that change if re-paired)
HA starttrigger: homeassistant with event: start
Complex condition becomes truetrigger: template (fires when the template turns true)

Avoid to: "on" without from when the entity can go unavailable -> on after a restart or network blip; use from: "off" or not_from: [unavailable, unknown].

ModeBehaviorUse for
single (default)Ignore new triggers while running (logs a warning)One-shot actions
restartStop the running instance and start overMotion lights with timers
queuedRun in order (max: default 10)Sequential notifications
parallelRun concurrentlyIndependent per-entity actions

Trigger IDs and choose

triggers:
  - trigger: state
    entity_id: binary_sensor.front_door
    to: "on"
    id: opened
  - trigger: state
    entity_id: binary_sensor.front_door
    to: "off"
    for: "00:00:30"
    id: closed
actions:
  - choose:
      - conditions:
          - condition: trigger
            id: opened
        sequence:
          - action: light.turn_on
            target: { entity_id: light.porch }
      - conditions:
          - condition: trigger
            id: closed
        sequence:
          - action: light.turn_off
            target: { entity_id: light.porch }

Templates

Prototype every template in Developer Tools -> Template first.

template:
  - sensor:
      - name: "Power cost today"
        unique_id: power_cost_today
        unit_of_measurement: "EUR"
        state_class: total
        state: >
          {{ (states('sensor.energy_today') | float(0) * states('input_number.kwh_price') | float(0)) | round(2) }}
        availability: >
          {{ has_value('sensor.energy_today') and has_value('input_number.kwh_price') }}
  - binary_sensor:
      - name: "Anyone home"
        unique_id: anyone_home
        state: "{{ states.person | selectattr('state', 'eq', 'home') | list | count > 0 }}"
  • Always give float(0)/int(0) defaults; states() returns strings and unknown.
  • Use availability: so a broken source makes the sensor unavailable instead of 0.
  • unique_id makes entities editable in the UI.
  • Prefer states('sensor.x') over states.sensor.x.state (the former handles missing entities).

Scripts with fields

script:
  notify_and_flash:
    alias: Notify and flash
    mode: queued
    fields:
      message:
        description: Text to send
        required: true
        selector: { text: {} }
    sequence:
      - action: notify.mobile_app_pixel
        data:
          message: "{{ message }}"
      - action: light.turn_on
        target: { entity_id: light.living_room }
        data: { flash: short }

Call with action: script.notify_and_flash and data: { message: "..." }.

Blueprints

blueprint:
  name: Motion light with timeout
  domain: automation
  input:
    motion_sensor:
      name: Motion sensor
      selector: { entity: { filter: { domain: binary_sensor, device_class: motion } } }
    target_light:
      name: Light
      selector: { target: { entity: { domain: light } } }
    delay:
      name: Off delay
      default: 120
      selector: { number: { min: 10, max: 1800, unit_of_measurement: seconds } }
mode: restart
triggers:
  - trigger: state
    entity_id: !input motion_sensor
    from: "off"
    to: "on"
actions:
  - action: light.turn_on
    target: !input target_light
  - wait_for_trigger:
      - trigger: state
        entity_id: !input motion_sensor
        to: "off"
  - delay: !input delay
  - action: light.turn_off
    target: !input target_light

Store under config/blueprints/automation/<author>/<name>.yaml. To use an input inside a template, assign it to a variable first (variables: { d: !input delay }).

Organization

  • Split config with packages: homeassistant: { packages: !include_dir_named packages }, one file per room or feature containing its automations, scripts, helpers, and templates.
  • UI-created automations live in automations.yaml; do not hand-edit while the UI editor is open.
  • Helpers (input_boolean, input_number, input_datetime, timers) make thresholds adjustable without editing YAML.

Debugging an automation that did not fire

  1. Settings -> Automations -> the automation -> Traces. No trace means the trigger never matched: check the entity ID and the exact state values in Developer Tools -> States ("on" vs on vs "open").
  2. Trace exists but stopped: the condition step shows which condition failed and why.
  3. mode: single warnings in the log mean it was already running.
  4. Numeric state triggers fire only when crossing the threshold, not if already above it.
  5. Is the automation enabled? Was it renamed (new entity ID) or disabled after errors?
  6. Raise logging temporarily:
logger:
  default: warning
  logs:
    homeassistant.components.automation: debug

Verification

  • Configuration check passes; automations reload without errors in the log.
  • Run the automation’s actions manually (Run actions) to confirm targets work.
  • Trigger it for real and read the trace step by step.
  • Restart HA and confirm nothing fires unexpectedly from unavailable -> on transitions.
  • For time-based automations, temporarily set the time trigger a few minutes ahead.

Gotchas

  • YAML booleans: quote "on"/"off"/"yes" in state comparisons.
  • Device triggers and device actions reference device IDs that change when a device is re-added; entity-based triggers survive better.
  • delay inside a mode: single automation blocks re-triggers for the whole delay.
  • Template triggers only fire on false -> true transitions; a template that is already true at reload does not fire.
  • Restarting HA cancels running automations and delays; use timers (timer.start) and restore-state helpers for long waits.

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc