Skip to content

Repository files navigation

AI Agent

Claude for Home Assistant

Chat with Claude from Home Assistant Assist, and call it from your automations — powered by the companion Claude Code add-on running on your own hardware. Requires Home Assistant OS or Supervised. Home Assistant lists the integration as AI Agent.

release release date HACS Default Home Assistant Python License: MIT

quality scale: platinum coverage: 100% Checked with mypy Ruff pre-commit

hassfest hacs lint tests secret-scan hygiene


claude_ha exposes Claude to Home Assistant itself. It talks to Claude through the Claude Code add-on over Home Assistant's internal network — the add-on holds the login and runs Claude, while this integration is a thin, secure client. Nothing here calls the Anthropic cloud directly.

💬 Conversation agent Talk to Claude from HA Assist — replies stream in live, it acts on benign requests immediately and confirms important ones (judged per action).
👁️ Camera vision Optionally, a visual question ("who's at the door?") sends one snapshot of an Assist-exposed camera to Claude. Off by default.
🗨️ Dashboard chat card A bundled Lovelace card to chat with Claude and Apply/Dismiss its suggestions.
⚙️ claude_ha.ask action Send a prompt to Claude from automations, optionally confirming changes via a phone notification.
📟 Sensors Add-on readiness, active model, and Claude token usage + prompt-API cost.
🩺 Health checks Detects the "chat can't see your home" gaps and raises a repair with the exact fix.
🎙️ Local voice one-click claude_ha.setup_voice installs Whisper + Piper and builds an Assist pipeline for your language.
🔒 Secure by design Read-only by default, bearer-token auth, scoped writes, no cloud calls.
🚀 Zero-touch setup Discovered, installed and started for you via the Supervisor.

Contents

How it works

HA Assist ─┐
           ├─► claude_ha (this integration) ──HTTP + bearer──► Claude Code add-on ──► Claude
Automation ┘        conversation entity                        (own agentic loop,
                    claude_ha.ask action                         scoped HA access)

The integration and the add-on are separate projects that connect through a small, versioned HTTP contract (a bearer-authenticated prompt server on the add-on's internal port). The connection details — host, port and a shared token — are handed to the integration automatically through Supervisor discovery, so there is nothing to type in.

Requirements

  • Home Assistant OS or Supervised (the integration manages a Supervisor add-on).

  • One companion add-on for the AI agent you want to talk to — Home Assistant doesn't ship either, so add the repository URL for the one you want to your add-on store once, then install the add-on from there:

    • Claude Code — https://github.com/LayerTM/ClaudeInHA
    • Codex — https://github.com/LayerTM/CodexInHA

    The integration's setup flow can also add the repository for you once you pick which agent to set up (Settings → Devices & services → Add integration).

Installation

HACS

This integration is listed in the HACS default store. A freshly listed repository takes a few hours to reach every HACS install, so the button below is the reliable route in the meantime — if your HACS doesn't know the repository yet, it offers to add it.

Open in HACS

  1. Click Open in HACS above and install AI Agent. (Accept the prompt if HACS asks to add the repository first.) You can also search HACS for claude-ha, which matches only this integration.
  2. Restart Home Assistant.
  3. Install the Claude Code or Codex add-on. Once it starts, Home Assistant offers to set up the AI Agent integration automatically — accept it. No configuration needed.

Neither route working? Add LayerTM/claude-ha as a custom repository (category Integration). If HACS replies that the repository already exists in the store, then it is already listed — search for claude-ha instead.

Manual

Copy custom_components/claude_ha into your Home Assistant config/custom_components/ directory and restart.

Configuration

Setup is zero-touch: when a companion add-on starts it advertises its host, port and a freshly generated token through Supervisor discovery, and Home Assistant surfaces a one-click setup. You can also add it from Settings → Devices & services → Add integration → AI Agent, which first asks which agent to set up, then finds, installs (offering to add its repository if needed) and starts the add-on for you. There are no options to fill in. If more than one add-on of the chosen agent is installed (for example a store build and a local build), setup asks which one to use; each one can be set up as its own entry, one agent per entry.

Each entry remembers which AI engine its add-on runs and checks it on every status poll. If the add-on ever reports a different engine, the entry goes unavailable and a repair explains why; nothing is sent to it until the engines match again.

Usage

Conversation agent

Select Claude (conversation.claude_code) as a conversation agent under Settings → Voice assistants, or target it directly. It answers in any language, and it can act on your home. With Prefer handling commands locally on, Home Assistant runs the device commands it recognises exactly (such as "turn off the kitchen light") itself, without the confirmation described below, and passes everything else — state questions included — on to Claude. Leave it off to have every request go through Claude. Answers stream in live, token by token (with the Claude Code add-on ≥ 1.17.0; older add-ons return the whole answer at once).

Every request is read first. If it would change state, Claude proposes the exact actions, and the integration decides — per action, from live entity metadata — whether to carry it out immediately or confirm:

  • Benign, low-risk actions run right away (e.g. turning a light on) and reply Done: ….
  • Important actions are held and confirmed with a plain yes / no — anything the deterministic classifier flags (locks, alarms, garage/door/gate covers, firmware updates, router/AP and other config entities, and opaque-effect wrappers like scenes, scripts and automations), anything Claude itself marks non-low-risk, or anything you pin as critical. The classifier only auto-runs an action it can positively prove benign — anything it can't resolve or bound falls back to confirmation. The confirmation replays the exact validated actions, so it never depends on the model remembering them.

Criticality is judged per action, not per domain — a shade and a garage door are both cover, but only the garage door is confirmed. Requires the Claude Code add-on ≥ 1.8.0 (older add-ons simply confirm everything). Tune it under the integration's Configure options: turn off auto-execute to confirm every change, or list entities that must always be confirmed.

Claude can only ever touch entities you have exposed to Assist — that exposure list is the outer ceiling; the per-action classifier is the inner gate.

Camera vision (opt-in, off by default; add-on ≥ 1.17.0). Turn on Let Claude look at cameras in the options and a clearly visual question — "who's at the door?", "look at the backyard camera" — sends one snapshot to Claude. Only a camera you have exposed to Assist is ever sent, only when the message is visual and exactly one camera resolves (by name, area or floor); it never guesses between cameras. The add-on fetches and downscales the snapshot itself; the integration passes only the entity id.

Dashboard card

A chat card ships with the integration — no separate install: the integration adds it to your dashboard resources (Settings → Dashboards → ⋮ → Resources) and keeps that entry current. If your resources are managed in YAML (lovelace: resource_mode: yaml), add it there yourself:

lovelace:
  resources:
    - url: /claude_ha/claude-chat-card.js
      type: module

Add a Manual card (or pick Claude Chat in the card picker) with:

type: custom:claude-chat-card
title: Claude

Type a message and the card shows Claude's reply. If Claude proposes a change, an inline Apply / Dismiss appears; Apply runs the confirmed write.

With more than one Claude entry, pick the one the card talks to in the card editor, or set its entry id in YAML (config_entry: <entry id>). Without it the card works only while a single entry exists.

The claude_ha.ask action

action: claude_ha.ask
data:
  prompt: Summarise today's calendar and suggest what to wear.
response_variable: claude

claude.text holds Claude's answer; the response also includes proposal (a described state change, or null), tools_used, and truncated.

To act on something, use the two-phase flow: a read call returns a proposal with intents; after your own confirmation, echo those exact intents back in a write call. Writes are scoped by the Claude Code add-on to just those confirmed intents — only use write from automations you control, never on untrusted input.

# 1. Ask (read) — get the proposed intents.
- action: claude_ha.ask
  data:
    prompt: Turn off everything in the garage.
  response_variable: claude
# 2. Confirm, then act (write) — echo the confirmed intents back.
- action: claude_ha.ask
  data:
    prompt: Turn off everything in the garage.
    mode: write
    intents: "{{ claude.proposal.intents }}"

Or let Home Assistant ask you to confirm on your phone: pass a notify target, and if Claude proposes a change you get an actionable Approve / Dismiss notification — Approve runs the confirmed write for you.

action: claude_ha.ask
data:
  prompt: Turn off everything in the garage.
  notify: mobile_app_my_phone

Entities

The full set the integration creates, all on one device. The first two are meant to be acted on; the rest are diagnostic, which Home Assistant keeps out of the device's main controls by default.

  • Claude (conversation.claude_code) — the conversation agent itself: pick it under Settings → Voice assistants, or target it from a script or automation. See Conversation agent.
  • Active alerts (binary_sensor.claude_code_active_alerts) — a problem binary sensor, on while the add-on has any active proactive alert (leak, offline, battery, CO₂ and the rest). It mirrors the add-on's own alert set, so your automations and your history can react to it instead of only the push notification. active_count, critical_count and an items list (each with its key, a critical flag and the one-line reading) are the attributes; critical marks the alerts the add-on always sends. The lines are your own entity names and readings — home data you already have in Home Assistant, never chat content. Needs add-on ≥ 1.39.0, and stays unavailable while proactive alerts are off or have not run yet.
  • Status (sensor.claude_code_status) — ready / initializing, with the add-on version, Claude version, the engine and its version, active model, whether a scoped HA MCP is configured and reachable, a health summary and the count of entities exposed to Assist as attributes.
  • Chat health (sensor.claude_code_chat_health) — a rolling summary of recent chat outcomes (ok / degraded). It asks whether failures are still happening, not whether any ever did. Three failed chats in a row raises it straight away — that's an outage, whatever the longer-run numbers say — and so does one chat in ten or more failing. It clears again once the clean chats since the last failure outnumber the failures behind them, or that failure is over 6 hours old. So a single old blip no longer nags for days, while a problem that is happening right now shows up before it has had time to move an average, and one chat getting through mid-outage isn't enough to call it recovered. The attributes show the working: recent/ok/degraded/recovered counts, the failure_rate the state turns on, consecutive_ok and consecutive_failed (chats since the last failure, and since the last success), the last degrade reason (a token, never prompt content) and the window's timestamps. Needs add-on ≥ 1.20.0; unavailable otherwise. The run counters and the timestamps need add-on ≥ 1.49.0 and are null without it — an add-on that reports none of them is judged on the rate alone, so a missing field neither quiets a warning nor invents one. window_dated (add-on ≥ 1.55.0, null without it) is how many of the recent chats carried a stamp, and it is what makes the two timestamps readable: equal to recent they span the whole window, and at 1 they are a single instant printed twice rather than a range.
  • Daily budget spend (sensor.claude_code_daily_budget_spend) — today's spend in USD against the add-on's daily budget, with limit / remaining / fraction_used / a soft near_cap flag as attributes (a limit of 0 = unlimited leaves those null). Diagnostic; never a repair. Needs add-on ≥ 1.21.0. If the add-on reports a spend or a cap that cannot be read as a plain amount, the sensor goes unavailable rather than showing a figure nobody can stand behind — an unreadable cap is never treated as no cap.
  • Token usage today (sensor.claude_code_token_usage_today) — today's input + output tokens (unit tokens), with the full usage report (per-period and per-model token totals, message counts) as attributes. Polled slowly (~5 min; the add-on caches it). If the add-on's usage history was lost and could not be recovered, the report carries history_reset and history_since, and a one-time repair notice under Settings → Repairs names the day totals now count from.
  • Prompt API cost (sensor.claude_code_prompt_api_cost) — the total prompt-API cost in USD (interactive-console use is measured in tokens, not dollars). The usage/cost sensors need the Claude Code add-on ≥ 1.7.0 and stay unavailable otherwise.
  • Account limits — how much of your Claude account's rate limits is used, across every machine and session signed in to it, not just this add-on: Session limit (sensor.claude_code_session_limit), Weekly limit (sensor.claude_code_weekly_limit) and one Weekly limit (model) sensor per model the account limits separately (e.g. sensor.claude_code_weekly_limit_fable). Each reads 0-100 %, with resets_at, severity and kind as attributes. The sensors are built from what the account reports, so a new limit appears on its own and one that stops being reported goes unavailable rather than disappearing. Accounts signed in with an API key have no such limits and get no sensors at all. Needs an add-on that provides account limits; older ones simply don't create these sensors.
  • Check Claude health (button.claude_code_check_claude_health) — runs the deeper probe described under Health checks on demand.

Entity ids above are the defaults (device Claude Code + entity name); adjust to your own if you've renamed them.

Usage dashboard

A ready-made overview using only built-in cards — paste it into a dashboard (Edit dashboard → Add card → Manual) to see cost, budget, chat health and MCP state at a glance. No extra install; adjust the entity ids if yours differ, and set the gauge max to your add-on's daily budget cap.

type: vertical-stack
cards:
  - type: entities
    title: Claude
    entities:
      - entity: sensor.claude_code_status
        name: Status
      - entity: sensor.claude_code_chat_health
        name: Chat health
      - entity: sensor.claude_code_daily_budget_spend
        name: Budget spent today
      - entity: sensor.claude_code_token_usage_today
        name: Tokens today
      - entity: sensor.claude_code_prompt_api_cost
        name: Total API cost
  - type: gauge
    entity: sensor.claude_code_daily_budget_spend
    name: Daily budget
    needle: true
    max: 5 # set to your add-on's daily budget cap
    severity:
      green: 0
      yellow: 3.5
      red: 4.5
  - type: history-graph
    title: API cost (24 h)
    hours_to_show: 24
    entities:
      - sensor.claude_code_prompt_api_cost
  - type: history-graph
    title: Tokens today (24 h)
    hours_to_show: 24
    entities:
      - sensor.claude_code_token_usage_today

Cost (USD) and tokens are on separate graphs on purpose — a single graph shares one y-axis, so the small USD range would be flattened against the much larger token count. The budget gauge is only meaningful with a daily budget set on the add-on; with no budget (unlimited) the spend still shows but the gauge scale is arbitrary.

Health checks

The most common "dead on arrival" failure is a chat that reaches Claude but can't actually see your home. The integration checks for this on every status poll and raises a repair with the exact fix when it finds:

  • Claude isn't logged in (the add-on is up but unauthenticated);
  • Claude has no Home Assistant token to read your home;
  • the Model Context Protocol Server integration is missing or unreachable;
  • nothing is exposed to Assist (so there's nothing to see or control).

These checks cost nothing (they read the status poll, not Claude). Press the Check Claude health button (button.claude_code_check_claude_health) to run a deeper probe — a tiny read that confirms Claude can actually reach the MCP server right now. Needs the add-on ≥ 1.14.0 for the reachability signal.

Local voice (one-click)

claude_ha.setup_voice turns local voice chat with Claude into one action. Give it a language and it installs and starts the official Whisper (speech-to-text) and Piper (text-to-speech) add-ons, then creates an Assist pipeline whose conversation agent is Claude:

action: claude_ha.setup_voice
data:
  language: uk        # uk, pl, en, de, …
  # tts_voice: uk_UA-ukrainian_tts-medium   # override the default voice
  # stt_model: small-int8                    # override the Whisper model

Then pick the new pipeline on your phone. Honest caveats: Whisper on CPU takes a few seconds per phrase, and some voices (Ukrainian especially) are weaker than others — pass tts_voice or switch to Cloud TTS if the default doesn't fit. The service returns the engines it wired and the pipeline id.

Security model

The security-critical work lives in the add-on; this integration deliberately does the least it can.

  • Bearer token, not network trust. Every request carries a token issued via Supervisor discovery. It never appears in the UI and is redacted from diagnostics.
  • Read-only by default. Chat is treated as untrusted input and runs deny-by-default; state changes require explicit, scoped, confirmed intents.
  • No cloud calls from HA. The integration only talks to the local add-on.

See SECURITY.md for the reporting policy, and the add-on for the full picture (env-scrubbed child processes, per-call statelessness, rate limiting, output redaction and audit logging).

How data is updated

The status sensor is refreshed by a DataUpdateCoordinator that polls the add-on's /api/status endpoint every 60 seconds. Prompts (chat turns and the ask action) are sent on demand.

Known limitations

  • Requires Home Assistant OS / Supervised — the add-on is a Supervisor add-on and is not available on Home Assistant Container or Core installs.
  • One Claude instance per Home Assistant (one add-on → one config entry).
  • The conversation agent surfaces proposed state changes but does not run a full confirm-and-act handshake from chat; use claude_ha.ask with mode: write from a trusted automation for that.

Troubleshooting

  • Entities unavailable for a few seconds after an add-on update or restart. Expected: while the add-on restarts its entities are unavailable and the log carries one INFO line, not an error; they come back within seconds of the add-on answering.
  • "The Claude Code add-on is not running". Raised when the add-on has been stopped for 10 minutes. The integration does not start the add-on on its own: the repair offers to start it, or start it from the add-on page. Home Assistant retries setup automatically once it runs.
  • "The add-on runs a different AI engine". The entry was set up for one engine and its add-on now reports another. Restore the add-on the entry was set up for, or delete the entry and add the add-on again.
  • A request is refused with a reason. "The request is too large" means the prompt or request exceeds the add-on's size limit (shown when the add-on publishes one); shorten it. "Can't make changes because it has no Home Assistant token" means the add-on options need a token before mode: write works. "Needs your explicit confirmation" means the action must be confirmed before it runs. "Took too long to answer" is a timeout; try again.
  • Setup keeps retrying. Check that the add-on is installed, started and healthy; the status endpoint must be reachable on the internal network.
  • Diagnostics. Download diagnostics from the integration's device page (the token is redacted) to inspect the last known status.

Removal

Delete the Claude integration from Settings → Devices & services. If Home Assistant installed the Claude Code add-on for you, remove it separately from the add-on store.

Development

python3.14 -m venv .venv && source .venv/bin/activate
pip install -r requirements_test.txt
python scripts/install_component_requirements.py
pre-commit install

ruff check custom_components tests scripts
ruff format --check custom_components tests scripts
mypy custom_components/claude_ha
pytest --cov=custom_components.claude_ha --cov-report=term-missing
python scripts/secret_scan.py .
docker run --rm -v "$PWD":/github/workspace ghcr.io/home-assistant/hassfest

CI runs hassfest, HACS validation, ruff, mypy, the test suite (100% coverage) and a secret scan on every push and pull request. See CONTRIBUTING.md.

Brand images live under custom_components/claude_ha/brand/ and are served by Home Assistant's Brands Proxy API (2026.3+) — no separate brands submission needed.

License

MIT © LayerTM

About

Chat with Claude from Home Assistant Assist and automations — via the Claude Code add-on. Conversation agent, claude_ha.ask action, status sensor. HACS-ready, quality-scale platinum.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages