Claude Media
Claude CodeでESPHomeのyaml設定を書き、esphome configで検証する

Claude CodeでESPHomeのyaml設定を書き、esphome configで検証する

ESPHomeのYAMLを編集するたびにesphome configを走らせ、失敗した出力をClaude Codeへ戻すhookの組み方です。名前の規則・ピン・ID重複・secretsの検出例つきです。

ESPHomeのYAMLは、スキーマに合わないキーやピンを書いても、書いた時点では何も起きません。間違いが分かるのは、コンパイルかアップロードの段階です。Claude Codeにこの設定を書かせるなら、編集のたびにesphome configを走らせ、失敗した出力をそのままClaude Codeへ返す構成が最短です。

esphome configは設定を検証して結果を表示するコマンドで、ファームウェアのコンパイルまでは行いません。PostToolUse hookでEdit|Writeの直後に呼べば、YAMLの誤りが次の一手の前に戻ってきます。手順は、検証コマンドの癖を知る、hookを組む、YAMLの分割ファイルで誤爆させない、の順です。

esphome configは何を返すか

ESPHome 2026.10.0を空のホームディレクトリで動かし、正常な設定と壊した設定の出力を比べました。ネットワーク接続も実機も使っていません。

esphome configの出口は2通りです。通れば終了コード0で、補完済みの設定全体を表示します。落ちれば終了コード2で、どのキーのどこが悪いかを表示します。

状況終了コードエラーの出る先
検証に通る終了コード0エラーの出る先補完後の設定がstdout
スキーマ違反(キー名・値・ピン)終了コード2エラーの出る先Failed config以下がstdout
読み込み時の失敗(secretsの未定義など)終了コード2エラーの出る先ERROR行がstderr

出力先が2つに割れる点が、hookを書くときの最初の落とし穴です。stdoutだけを拾うとsecretsの未定義を見逃し、stderrだけを拾うとスキーマ違反を見逃します。2>&1でまとめて受けます。

手元で出たエラーの例

次のYAMLは、キー名をnmeと打ち間違えています。

sensor:
  - platform: uptime
    nme: Uptime

esphome configの出力は次のとおりです(ESPHome 2026.10.0)。

Failed config
 
sensor.uptime: [source bad1.yaml:10]
  platform: uptime
 
  [nme] is an invalid option for [sensor.uptime]. Did you mean [name]?
  nme: Uptime

「Did you mean [name]?」まで付くので、Claude Codeはこの出力だけで直せます。ほかに手元で出たエラーは次のとおりです。

  • ピン名の解決失敗: esp32devにpin: D4と書くとCannot resolve pin name 'D4' for board esp32dev.が出ます。D4のような別名は、ボード定義にある場合だけ使えます(ESPHomeのPinの説明に、NodeMCU ESP8266のD0〜D8が例として載っています)
  • フラッシュ用ピンの誤用: ESP32でpin: GPIO6と書くと、フラッシュインターフェースと衝突するという趣旨のエラーが出ます。ピン番号は文法的に正しいので、人間のレビューでも見落としがちな誤りです
  • ID重複: 同じid: upを2つのセンサーに付けるとID up redefined!が出ます。IDはC++の変数名として使われるため、重複できません
  • secretsの未定義: !secret missing_keyでSecret 'missing_key' not definedが、該当ファイルの行・列つきで出ます

名前の規則も検証に含まれます。esphome: name:にLiving_Roomと書くと、大文字Lが不正な文字として指摘されました。この版のエラー文は使える文字に_を含めて案内していますが、ESPHomeのコア設定のページは「小文字・数字・ハイフンのみ、既定で24文字まで」と説明しています。ページとエラー文の食い違いは、実際の判定をesphome configに任せる理由になります。

nameの上限は既定で24文字で、name_add_mac_suffixをfalseにすると31文字まで伸びます。Living_Roomのように大文字や_を使いたい場合の置き場所がfriendly_nameです。nameをliving-roomにして、friendly_name: Living Roomと書けば、Home Assistantにはこちらがデバイス名として渡され、エンティティ名の前にも必要に応じて付きます。省略しても動きますが、Home Assistant側の名前が分かりにくくなります。Claude Codeに「表示名を直して」と頼むときは、nameではなくfriendly_nameを触るよう伝えておくと、ノード名の変更で端末の識別が変わる事故を避けられます。

もう1つ、esphome:ブロックにはmin_versionがあります。この設定をコンパイルするのに必要なESPHomeの最低バージョンを書くキーで、min_version: 2025.11.0のように指定します。手元のESPHomeがこれより古いと、新しいコンポーネントや書式が使えず、configが落ちます。Claude Codeが新しい書式を持ち込んだときに、エラーの原因が記述ミスではなくESPHome本体の古さだと切り分けられるので、複数の端末や複数人でYAMLを共有する場合は書いておく価値があります。

1回で全部は返ってこない

nmeの誤りとLiving_Roomの誤りを同じファイルに入れたところ、最初の実行では名前の誤りだけが返り、nmeは名前を直したあとの実行で初めて出ました。エラーは段階的に出ます。

つまり、1回の編集でesphome configが通るまで、hookが複数回走る前提になります。1回で全部直るとは考えず、「通るまで繰り返す」指示をCLAUDE.mdに書いておくと、Claude Codeが途中で諦めにくくなります。

PostToolUse hookで自動検証する

hookの要点は2つです。1つは、PostToolUseの終了コード2でstderrがClaudeに渡ること。もう1つは、PostToolUseではツールがすでに実行済みなので、ファイルの書き込み自体は止められないことです。Claude Codeのhooksのドキュメントも、PostToolUseは「stderrをClaudeに見せる。ツールはすでに実行済み」と説明しています。書き込みを止める用途には向かず、「書いた直後に結果を戻す」用途に合います。同じ構成をPythonの設定管理に当てた例はHydraの設定合成エラーの記事にあります。

スクリプトを置く

.claude/hooks/esphome-config.shに次の内容を置きます。jqと、PATHに通ったesphomeコマンドが前提です。

#!/bin/bash
# PostToolUse(Edit|Write): 編集したESPHomeのYAMLを検証する
f=$(jq -r '.tool_input.file_path // empty')
case "$f" in
  *.yaml|*.yml) ;;
  *) exit 0 ;;
esac
# 共有ファイルは単体で検証できないので除外する
case "$(basename "$f")" in
  secrets.yaml|common*.yaml) exit 0 ;;
esac
# 先頭に esphome: ブロックを持つ YAML だけ対象にする
grep -q '^esphome:' "$f" || exit 0
 
out=$(esphome config "$f" 2>&1)
if [ $? -ne 0 ]; then
  echo "esphome config が失敗しました: $f" >&2
  echo "$out" | grep -v '^INFO' | head -40 >&2
  exit 2
fi

grep -v '^INFO'で、毎回出るバージョン表示と読み込み中の行を落としています。head -40は、ID重複のエラーが設定値を延々と続けて出すためです。上限なしだと、Claude Codeの文脈に不要な行が積まれます。

実行権限を付けます。

chmod +x .claude/hooks/esphome-config.sh

settings.jsonに登録する

.claude/settings.jsonに登録します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/esphome-config.sh"
          }
        ]
      }
    ]
  }
}

matcherのEdit|Writeはツール名に対する条件です。Bash経由でYAMLを書き換えた場合は拾いません。

hookを単体で試す

Claude Codeを介さずに、hookが受け取るJSONを手で渡して動作を確かめられます。先ほどの壊れたYAMLで試すと、終了コード2とstderrの本文が出ました。

echo '{"tool_input":{"file_path":"'$PWD'/bad1.yaml"}}' \
  | .claude/hooks/esphome-config.sh; echo "exit=$?"

正常なYAMLでは何も出ず、終了コードは0でした。この「静かに通る」挙動が大事で、通っているあいだはClaude Codeの文脈を消費しません。

分割したYAMLで誤爆させない

ESPHomeはYAMLを!includeやpackages:で分割できます。分割した断片はesphome:ブロックを持たないため、単体でesphome configにかけると失敗します。

sensor:
  - platform: uptime
    name: U

このファイルをesphome config part.yamlに渡すと、'esphome' section missing from configuration.と返りました。上のスクリプトが先頭のesphome:行を条件にしているのは、このためです。

ただし、この条件だと断片側の誤りは検証されません。断片を編集したときは、親になるYAMLを検証する運用が要ります。親が1つなら、スクリプトのesphome config "$f"を親ファイル固定に書き換えます。複数の端末を1つのリポジトリで管理しているなら、CLAUDE.mdに「common/以下を編集したら、各端末のYAMLをesphome configで検証する」と書いてClaude Codeに回させるほうが現実的です。

マージキーの落とし穴

分割に<<: !includeを使うと、ESPHomeのコア設定のページによれば、同じトップレベルのキーを複数のファイルが定義したとき、2つ目以降は丸ごと捨てられます。たとえばa.yamlとb.yamlが両方api:を持つと、b.yamlのapi:は消えます。既定ではこの破棄に警告が出ます。

キーを結合したい場合はpackages:を使います。packages:は再帰的な深いマージを行うと説明されています。Claude Codeに分割を頼むときは「<<: !includeではなくpackages:を使う」と指定しておくと、設定が黙って消える事故を避けられます。警告を止めるmerge_warnings: falseは、意図的な破棄のときだけにします。

YAMLの確認をどこまで任せるか

esphome configが通ることと、実機で動くことの間には距離があります。

確認の段階コマンドhookに向くか
スキーマ・ピン・ID・secretsの検証コマンドesphome confighookに向くか向く(手元では0.3秒前後)
C++へ変換してコンパイルコマンドesphome compilehookに向くか向かない(時間がかかる)
実機への書き込みコマンドesphome upload / runhookに向くか人が実機を前にして実行する

esphome compileは検証に加えてファームウェアをコンパイルします。時間のかかるコマンドをEdit|Writeのたびに走らせると、編集の流れが止まります。コンパイルは、YAMLが固まったあとにClaude Codeへ「esphome compileを実行して結果を報告して」と頼むか、停止時に走らせる別のhookに分けるのが扱いやすい構成です。停止時に検証を走らせる組み方は、Claude Code hooksでテストを自動実行すると同じ考え方です。

secretsをClaudeに読ませない

esphome configの表示は、既定で!secretの値を伏せます。--show-secretsを付けたときだけ値が出るため、hookやClaude Codeに渡すコマンドには付けません。YAMLの正しさを見るだけなら--no-defaultsが便利で、手元の実行では!secret 'wifi_ssid'の参照のまま、書いたキーだけが出ました。補完済みの設定は数百行になるので、差分確認や再現用の最小YAMLを作るときに向きます。

また、hookの除外条件にsecrets.yamlを入れているのは、このファイルが単体のESPHome設定ではないからです。値の中身を検証する対象でもありません。

CLAUDE.mdに書いておく規約

hookが検証を担うので、CLAUDE.mdには検証で拾えない決まりごとを書きます。

## ESPHome
- YAMLを編集したら、hookのesphome configが通るまで直す
- 分割には packages: を使い、<<: !include は使わない
- 表示名は friendly_name に書き、name は小文字・数字・ハイフンだけにする
- 新しい書式を使うときは esphome: の min_version を合わせて更新する
- 秘密情報は !secret で参照し、secrets.yaml は読まない・編集しない
- GPIOはボード名のエイリアスではなく GPIO番号で書く
- 実機への upload / run は人が行う。実行しない

最後の行は、書き込み先の機器に影響する操作を、人の手に残すための線引きです。hookはスキーマの誤りを返す仕組みで、実機に対して安全かどうかは判定しません。

関連して、Home Assistant側のカスタム統合を作る場合はHome Assistantのカスタムコンポーネントのように検証手段が別になります。YAMLの自動修正という観点では、PostToolUse hookのLint自動修正が言語別のコマンドを並べています。

まとめ

esphome configは手元の小さな設定で0.3秒前後で終わり、終了コード2とエラー本文でClaude Codeに修正点を渡せるので、Edit|Write直後のPostToolUse hookと相性が良い検証です。stdoutとstderrの両方を拾うこと、esphome:を持たない断片を除外すること、esphome compileは別の段階に分けることの3点で、誤爆と待ち時間を抑えられます。実機に書き込む前の最後の確認は、これまでどおり人の手に残ります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. esphome.io/components/esphome.html
  2. esphome.io/guides/cli
  3. esphome.io/guides/configuration-types
  4. code.claude.com/docs/en/hooks-guide
  5. code.claude.com/docs/en/hooks
この記事を共有:XはてブLinkedIn