If you run Home Assistant 2026.9 or later, this release fixes a security regression: lock commands were skipping the critical-action PIN.
HA 2026.9 renamed every built-in LLM tool from a bare intent name to a namespaced one (HassTurnOff became intent__HassTurnOff, GetLiveContext became homeassistant__GetLiveContext). This integration compared bare names in several places, so four things stopped working with no error in the log: tool retrieval, the lock PIN gate, the actuation classifier, and GetLiveContext binding. Everything below follows from that one rename.
Upgrading needs no action. Stale index rows are removed on the next restart.
Fixed
- Tool retrieval works again on Home Assistant 2026.9. That release renamed every built-in LLM tool from a bare intent name to a namespaced one —
HassTurnOffbecameintent__HassTurnOff,HassLightSetbecamelight__HassLightSet,GetLiveContextbecamehomeassistant__GetLiveContext— and the tool index has never deleted anything, so upgraded installs carried a complete shadow copy of the old names. Those dead rows embed almost identically to their live twins and score slightly higher (same text, one prefix shorter), and the vector search cuts to the configured retrieval limit before the filter that drops dead rows runs. The result: the top five hits were four renamed-away rows and one live one, and the model was handed almost no usable tools. Field report: "turn on the garage lights" worked, and the follow-up "turn it off" came back with one media-player tool and no way to turn the light off. Dead rows are now dropped before the limit is applied, and the startup index pass deletes rows for tools that no longer exist (scoped to providers that enumerated cleanly, so an API that failed to load never loses its tools). No action is needed: the stale rows are removed on the next restart. - The critical-action PIN gate covers locks again.
HassTurnOn/HassTurnOffaimed at a lock are treated as critical even though they carry no service argument, but that rule was keyed on the bare tool name, so on HA 2026.9 a lock command arriving asintent__HassTurnOffskipped PIN verification entirely. Affects installs with the critical-action PIN enabled on HA 2026.9 or later. The alarm-panel carve-out (panels use their own code, never the generic PIN) is matched the same way and was equally affected. - Home Assistant actuation tools are recognised as actuation again. The classifier matched names against intent prefixes (
HassTurn,HassLight, …) but namespaced names start with the domain, so every HA control tool was classified read-only. That silently disabled the actuation-safety retrieval pass, the actuation strip on read-only "is the door open?" questions, the fallback ordering that puts control tools first for a control request, and the recovery path after a rejected tool call. GetLiveContextis bound again. The agent force-injects it on every turn so the model can always check live state — the system prompt calls it a primary function — but the injection looked the tool up by its bare name, found the pre-rename row, and discarded it as not live. On an upgraded install the model was told to call a tool it had never been given.- Follow-up commands that point back at the previous turn work. "Turn it off" right after "turn on the garage lights" carries no entity, domain, or target of any kind, so ranking tools on those three words landed wherever the phrasing happened to point: five media-player tools, and no way to turn the light off. The model had it right — it asked for the light by name — and the request was refused because the tool had not been retrieved. When a turn is short and leans on a referential word ("it", "them", "that", "again"), the previous user turn is now added to the query used to rank tools. What the model is shown is unchanged, both turns are also searched separately so a tool only the new phrasing finds is never displaced, and the widening is kept out of every intent check — otherwise "close it" after "is the garage door open?" would read as a state question and lose its actuation tools.
- Per-tool exclusions work again. The exclusion list matched tool names exactly, so an exclusion saved before the rename no longer matched the tool it was meant to switch off: the tool came back into the model's hands while the options picker still showed it as excluded. A stored name that carries no namespace is now also matched against the live tool's base name, so excluding
HassTurnOffkeeps excluding it asintent__HassTurnOff. A stored name that already carries a namespace is matched exactly and never widened, so switching off one tool can never switch off a same-named tool from another integration. Enforcement, tool retrieval and the always-included list all share one definition of a match. - The alarm and lock argument normalizers, which turn a turn-on/turn-off intent into the right service call, match the namespaced names too. The lock payload now always carries the target's name: Home Assistant's turn-on/turn-off intents resolve targets from name, area and floor and never read an
entity_id, and a domain on its own is enough to satisfy their "cannot target all devices" guard, so a payload of domain plus entity id alone matched every exposed lock. A single lock command now names a single lock.
Verified on a live install
Home Assistant 2026.9.1, before release: 31 stale index rows evicted on first restart; "turn on the garage light" then "turn it off" now turns the light off; unlocking a lock prompts for the PIN and affects only the named lock.
Known limitations
Tracked in TODOS.md, not fixed here:
- The index eviction sweep runs on the awaited startup path with no timeout, and an index row with a malformed value would leave the tool index unready until the next restart. This sweep runs on every install's first restart after updating.
- An LLM API that enumerates successfully but returns an empty tool list has all of its index rows deleted until the next restart.
- Tool-name matching strips any
__prefix, so an MCP server tool named e.g.foo__GetLiveContextis treated as the Home Assistant tool. Dispatch is unaffected and the lock PIN case fails closed. - A stored tool exclusion keeps its pre-rename spelling, so the options picker labels it "(not currently available)" even though it is being enforced.
Full changelog: v3.41.0...v3.41.1