Home Assistant skill for Codex

Automations that fire once, at the right time. Codex writes Home Assistant YAML in the current triggers, conditions and actions syntax, gives every automation a stable id and a deliberate mode, and guards against the unavailable-state transitions that make automations fire after restarts. It validates config before reloading, debugs through traces, and scaffolds async custom integrations with config flows and a coordinator.

  • Embedded & IoT
  • $home-assistant
  • 198 lines
  • 2 files
  • ~1,950 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- home-assistant

Installs to ~/.agents/skills/home-assistant/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $home-assistant, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes Home Assistant automations, scripts, template sensors, blueprints and packages in current YAML syntax (triggers/conditions/actions, action instead of service), picks automation modes, validates config before restart, and scaffolds custom integrations with config flows and DataUpdateCoordinator. Use when an automation does not fire or fires twice, converting old YAML that uses platform and service keys, building template sensors, organising configuration.yaml into packages, or writing a custom_components integration for a local device or API.

Use it when

  • An automation does not fire, fires twice or fires after restarts
  • Converting older YAML to the triggers/actions and action syntax
  • Building template sensors, blueprints or a packages layout
  • Writing a custom_components integration for a local device

Use something else when

  • The problem is in the MQTT broker, topics or ACLs underneath Home Assistant: use mqtt-iot
  • You are writing the firmware of an ESP32 device itself: use esp32-firmware

How to check the result

Run the config check, reload only the changed domains, and read the automation trace of a real or manually triggered run to confirm the expected path.

The mistake it prevents

A state trigger with to: "on" and no from: also fires on unavailable to on after every restart or Wi-Fi drop.

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

--- name: home-assistant description: Writes Home Assistant automations, scripts, template sensors, blueprints and packages in current YAML syntax (triggers/conditions/actions, action… license: MIT ---

Done well means: automations use current syntax with a stable id, the right mode, and conditions that make them safe to fire twice; configuration passes the checker before any restart; and custom integrations are async, configured through the UI and never block the event loop.

Inspect first

  1. Read AGENTS.md, then the config tree: configuration.yaml, automations.yaml, scripts.yaml, packages/, custom_components/, secrets.yaml (do not print its values).
  2. Find the Home Assistant version (.HA_VERSION in the config folder). Syntax below is for 2024.10 and later; older installs need the legacy keys.
  3. Ask for the real entity IDs (Developer tools > States, or GET /api/states). Never invent light.living_room; a wrong entity ID fails silently at runtime.

Current syntax

LegacyCurrent (2024.8 / 2024.10+)
trigger: list with platform: statetriggers: list with trigger: state
condition:conditions:
action: list with service: light.turn_onactions: list with action: light.turn_on
data_template:, service_template:Templates work directly in data: and action:

Old keys still load, but write new code in current syntax and convert a file at a time.

Automation pattern

- id: hallway_motion_light            # stable id: enables UI editing and traces
  alias: Hallway light on motion after dark
  mode: restart                       # new motion restarts the off-timer
  triggers:
    - trigger: state
      entity_id: binary_sensor.hallway_motion
      to: "on"
  conditions:
    - condition: state
      entity_id: sun.sun
      state: below_horizon
    - condition: state
      entity_id: input_boolean.guest_mode
      state: "off"
  actions:
    - action: light.turn_on
      target:
        entity_id: light.hallway
      data:
        brightness_pct: "{{ 30 if now().hour >= 23 or now().hour < 6 else 80 }}"
    - wait_for_trigger:
        - trigger: state
          entity_id: binary_sensor.hallway_motion
          to: "off"
          for: "00:02:00"
      timeout: "00:30:00"
    - action: light.turn_off
      target:
        entity_id: light.hallway

Choosing the mode

ModeUse when
single (default)One run at a time; extra triggers are dropped with a warning
restartMotion lights, debounced reactions: latest trigger wins
queuedEvery trigger must run in order (counting, notifications per event)
parallelIndependent runs per trigger, e.g. per-entity reactions with trigger.entity_id

Triggers that behave

  • to: "on" without from: also fires on unavailable -> on after a restart or Wi-Fi blip. Add from: "off" when that matters, or a not_from: ["unavailable", "unknown"].
  • State values are strings: quote "on", "off", numbers in state: conditions.
  • numeric_state with above/below fires only when crossing the threshold, not while staying above it.
  • Use for: on triggers to debounce, not delay in actions.
  • Add trigger IDs (id: arrived) and branch with choose instead of writing near-duplicate automations.
  • Time-based logic: trigger: time with an input_datetime helper so users can change it in the UI.

Template sensors

template:
  - sensor:
      - name: Average upstairs temperature
        unique_id: avg_upstairs_temp
        unit_of_measurement: "°C"
        device_class: temperature
        state_class: measurement
        availability: >
          {{ has_value('sensor.bedroom_temp') and has_value('sensor.office_temp') }}
        state: >
          {{ ((states('sensor.bedroom_temp') | float + states('sensor.office_temp') | float) / 2)
             | round(1) }}
  • Always provide availability (or float(default)) so a missing sensor shows unavailable instead of crashing the template or recording a false zero.
  • unique_id makes the entity editable in the UI; state_class enables long-term stats.
  • Test templates in Developer tools > Template before saving.

Organising config

# configuration.yaml
homeassistant:
  packages: !include_dir_named packages     # packages/heating.yaml, packages/security.yaml
automation: !include automations.yaml       # UI-managed file; keep it
  • One package per feature holds its helpers, template sensors, scripts and automations.
  • Credentials go in secrets.yaml via !secret; it must stay out of git. Add it to .gitignore before the first commit.
  • Reusable patterns become blueprints in blueprints/automation/<author>/<name>.yaml with !input selectors.

Validate before restarting

ha core check                                  # Home Assistant OS / Supervised
docker exec homeassistant python -m homeassistant --script check_config -c /config   # container

Then reload only what changed (Developer tools > YAML: automations, scripts, templates) instead of restarting. Ask before restarting Home Assistant on a live home: locks, alarms and climate automations stop during the restart.

Debugging an automation

  1. Open its trace (Settings > Automations > the automation > Traces): shows which trigger fired, which condition failed and every action’s result. Needs the id.
  2. No trace at all: the trigger never fired. Compare the trigger with the entity’s actual state history; check for typos in entity IDs.
  3. Check the log for Already running (mode single) and template errors.
  4. Fires twice: two automations on the same trigger, a template trigger flapping, or unavailable transitions; look at the state history around the event.

Custom integration skeleton

custom_components/acme_meter/
├── __init__.py          # async_setup_entry / async_unload_entry
├── manifest.json
├── config_flow.py       # UI setup; validates host and credentials
├── coordinator.py       # DataUpdateCoordinator polling the device
├── sensor.py            # CoordinatorEntity subclasses with unique_id
└── strings.json         # config flow text (plus translations/en.json)
{
  "domain": "acme_meter",
  "name": "Acme Meter",
  "version": "0.1.0",
  "codeowners": ["@your-handle"],
  "config_flow": true,
  "documentation": "https://github.com/your-org/acme-meter",
  "iot_class": "local_polling",
  "requirements": ["acme-meter-client==1.4.2"]
}
  • Everything is async. Blocking I/O (requests, serial, sleep) on the event loop freezes the whole house; use an async client or hass.async_add_executor_job.
  • One DataUpdateCoordinator per device fetches once and all entities read from it; raise UpdateFailed on errors so entities go unavailable rather than stale.
  • Raise ConfigEntryNotReady in setup when the device is offline; Home Assistant retries.
  • Give every entity a unique_id derived from the device serial, not the IP address.
  • Test with pytest-homeassistant-custom-component; installing it needs network access.

Verification

  • Config check passes; only the changed domains reloaded
  • Trace of a real (or manually triggered) run shows the expected path
  • Restart test: no spurious firing from unavailable transitions
  • Template sensors show unavailable, not 0, when a source sensor is offline
  • Custom integration: no “Detected blocking call” warnings in the log

Report

**HA version:** 2025.9.2 (container)
**Changed:** packages/lighting.yaml (3 automations converted to triggers/actions syntax,
mode restart, from: "off" guard), template sensor with availability
**Checks:** check_config clean; automations reloaded; trace shows condition sun below_horizon pass
**Not done:** no restart performed; alarm automations untouched

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Home Assistant"
  short_description: "Automations that fire once, at the right time"
  default_prompt: "$home-assistant My hallway motion automation turns the light on after every Home Assistant restart, fix it"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem