This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Home Assistant configuration repository, not a software project. It's a declarative YAML configuration for a real smart home, deployed via Docker Compose (see https://github.com/kylegordon/ha-stack). There is no build step — "development" means editing YAML and validating it against real Home Assistant / ESPHome binaries in Docker.
The config is an amalgamation of examples gathered from around the internet (BRUH Automation, HA community forum posts, etc.) rather than a from-scratch design, so don't be surprised by inconsistent style between older and newer packages.
Always run these before considering a YAML change complete.
YAML lint (required for any YAML change):
yamllint -c .github/yamllint-config.yml .Rules disabled: line-length, comments-indentation, document-start, indentation. Ignores custom_components/, www/lovelace-auto-entities/, esphome/common/colours.yaml.
Markdown lint (required for .md changes):
remark --no-stdout --color --frail --use preset-lint-recommended .(or via Docker: docker run --rm -v $(pwd):/src pipelinecomponents/remark-lint:latest remark --no-stdout --color --frail --use preset-lint-recommended .). Respects .remarkignore.
Home Assistant config check (required if you touch HA config — root files, packages/, automation/, etc.):
cp travis_secrets.yaml secrets.yaml
touch fullchain.pem privkey.pem
docker run --rm -v $(pwd):/config homeassistant/home-assistant:stable \
python -m homeassistant --config /config --script check_config --info allsecrets.yaml is a symlink to travis_secrets.yaml in the working tree already, but CI does a real copy — do the same when testing so you don't accidentally edit the template. CI runs this same check against stable, beta, rc, and dev images; swap the tag to reproduce a specific CI failure.
ESPHome validation (required if you touch anything under esphome/):
cp esphome/travis_secrets.yaml.txt esphome/common/secrets.yaml
cp esphome/travis_secrets.yaml.txt esphome/secrets.yaml
docker run --rm -v $(pwd):/config esphome/esphome:stable config /config/esphome/<device>.yamlCI validates every esphome/*.yaml device in a matrix, against both stable and beta ESPHome images.
Never commit secrets.yaml, esphome/secrets.yaml, esphome/common/secrets.yaml, fullchain.pem, or privkey.pem — all are gitignored. travis_secrets.yaml / esphome/travis_secrets.yaml.txt are the commit-safe templates with dummy values; that's also what CI uses, hence the "travis" name (a holdover from Travis CI).
configuration.yaml is the entry point and wires everything together via YAML include directives. Knowing which directive a directory uses tells you the expected shape of files inside it:
!include_dir_named packages—packages/*.yaml, each file is a named dict; this is where most real configuration lives (see below).!include_dir_list automation—automation/*.yaml, each file is one list item (a single automation).!include_dir_merge_named scripts/—scripts/*.yaml, merged into one named dict.!include_dir_named input_select,input_boolean— same named-dict pattern.!include_dir_list scenes—scenes/*.yaml.!include some_file.yaml— single-file includes for the simple entity domains (sensors.yaml,lights.yaml,switches.yaml,climate.yaml,mqtt.yaml,template.yaml,binary_sensors.yaml,media_players.yaml,device_trackers.yaml,groups.yaml,zones/places.yaml,shell_commands.yaml,notify.yaml,persons.yaml,recorder.yaml,logger.yaml).
packages/ (44+ files) is where most logic lives, and it's organized by room or by feature, not by HA domain. A single package file (e.g. packages/kitchen.yaml) typically bundles together everything for that room: automations, scripts, template sensors, input helpers, etc. When changing behavior for a room/feature, check for an existing package file with that name first rather than scattering changes across the domain-level files (lights.yaml, sensors.yaml, ...).
Notable packages beyond simple rooms: adaptive_lighting.yaml, alarm.yaml, climate.yaml + heatpump.yaml (Better Thermostat-based heating), givenergy.yaml (battery/solar), stove.yaml (HWAM wood stove), valetudo.yaml (self-hosted vacuum), overflights.yaml, bin_reminder_tts.yaml, device_alerts.yaml.
esphome/*.yaml — one file per physical device (70+). Shared behavior lives in esphome/common/*.yaml and is pulled in via !include common/<file>.yaml from each device file — check there before duplicating logic across devices (e.g. power_plug_common.yaml, tx_ultimate_easy_common.yaml, wemos_pir_common.yaml, tin-hut-doors.yaml).
TX-Ultimate-Easy touch switches (*_switch.yaml) are a significant device family: they fire esphome.tx_ultimate_easy events (device_name, action — click/double_click, optional button_id for multi-gang) consumed by automations in the corresponding packages/*.yaml file (e.g. study_switch.yaml events → packages/study_lights.yaml). When wiring up a new switch, look at how an existing room's switch+package pair is connected before inventing a new pattern. These switches run in "API Failsafe only" mode so the physical relay still works if HA/WiFi is down.
The Somfy RTS garage/tin-hut door control (tin_hut_door_left.yaml / tin_hut_door_right.yaml) shares state-machine logic from esphome/common/tin-hut-doors.yaml — it models a single-relay cover as a stop/open/close cycle with time-based position tracking, not true position control.
custom_components/ holds integrations not available (or not current) in HACS/core: hwam_stove (wood stove, needs pystove==0.3a1), adaptive_lighting, programmable_thermostat, thermal_comfort, bulb_energy, smartir, alexa_media, plus hacs itself. This directory is excluded from yamllint and remark lint.
.github/workflows/main.yaml— yamllint + remarklint run first; fourhome_assistant_*jobs (stable/beta/rc/dev) run in parallel afterward, each spinning up the matchinghomeassistant/home-assistantDocker image and runningcheck_config..github/workflows/esphome-parallel.yaml— triggered only onesphome/**changes; discovers allesphome/*.yamldevice files and matrix-builds each against bothesphome/esphome:stableand:beta. Thefinaljob is what branch protection actually checks..github/workflows/esphome-dummy.yaml— provides a passingfinaljob when a PR doesn't touchesphome/, so branch protection isn't blocked.- Everything also runs daily (scheduled) to catch upstream HA/ESPHome release breakage even with no code changes.
- Prefer extending an existing room/feature package over adding new top-level entity files.
- When adding a new ESPHome device of a type that already exists (another power plug, another touch switch, another PIR sensor), copy the pattern of a similar existing device file and its
common/include rather than writing config from scratch. - Entity-level cosmetic tweaks (icons, friendly names,
assumed_state) live inconfiguration.yamlunderhomeassistant: customize:— check there before adding afriendly_nameelsewhere.