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.
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. |
- How it works
- Requirements
- Installation
- Configuration
- Usage
- Security model
- How data is updated
- Known limitations
- Troubleshooting
- Removal
- Development
- License
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.
-
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).
- Claude Code —
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.
- 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. - Restart Home Assistant.
- 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.
Copy custom_components/claude_ha into your Home Assistant
config/custom_components/ directory and restart.
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.
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.
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: moduleAdd a Manual card (or pick Claude Chat in the card picker) with:
type: custom:claude-chat-card
title: ClaudeType 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.
action: claude_ha.ask
data:
prompt: Summarise today's calendar and suggest what to wear.
response_variable: claudeclaude.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_phoneThe 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) — aproblembinary 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_countand anitemslist (each with itskey, acriticalflag and the one-line reading) are the attributes;criticalmarks 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, ahealthsummary 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, thefailure_ratethe state turns on,consecutive_okandconsecutive_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 therecentchats carried a stamp, and it is what makes the two timestamps readable: equal torecentthey 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, withlimit/remaining/fraction_used/ a softnear_capflag 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 (unittokens), 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 carrieshistory_resetandhistory_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 %, withresets_at,severityandkindas 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.
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_todayCost (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.
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.
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 modelThen 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.
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).
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.
- 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.askwithmode: writefrom a trusted automation for that.
- 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: writeworks. "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.
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.
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/hassfestCI 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.
MIT © LayerTM