fix: triage all 10 ha_search_entities behaviors from #1170#1195
Conversation
…#1170 Closes homeassistant-ai#1170. Closes homeassistant-ai#1166. Each finding from the umbrella issue is addressed: 1. domain_filter case-insensitive — silent zero-result on "Light" was normalized away at the boundary; the response echoes the canonical lowercase value. 2. Token elision — `bedlight` no longer ties bed_light against five unrelated `*_lights` at score 76. Per-entity, the entity_id tail and friendly_name now contribute their separator-stripped concat as a single high-IDF BM25 token. 3. area_only response shape — results carry `score=100` and `match_type="area_match"` to match the four other search-type branches. 4. Per-result `area_filter` echo dropped on `area_filtered_query` — top-level field still echoes; per-result was redundant and asymmetric vs the other branches. 5. Multi-token coverage gate — `xyz_irrelevant_garbage` no longer surfaces `cover.garage_door` at score 92 via the `garbage~garage` ratio. typo_fallback now requires ≥50% of distinct query tokens to fuzzy-match a doc token; single-token typos like `ligth` are unaffected. 6. `_partial_results_search` removed — the "last resort" fallback returned every entity at score 0 with `partial: true`, masking real errors. Exceptions now propagate so callers see the cause. 7. area_only aggregates ALL fuzzy-matched areas (was first-match-wins, and from a `set` so non-deterministic). Public-API change: new `area_names: list[str]` joins legacy `area_name` (kept as the first match for one minor version of compat). 8. Aliases — entity registry aliases are now folded into the BM25 corpus (one extra `config/entity_registry/get_entries` round-trip after the hidden filter), and area registry aliases are consulted in `get_entities_by_area`. Alias-driven matches surface as `match_type="alias_match"`. 9. Hidden-by filter — new `include_hidden: bool = False` parameter on `ha_search_entities`. Defaults skip entities where `entity_registry.hidden_by` is set (UI-hidden infra entities, diagnostic helpers); set True for diagnostics workflows. 10. Test coverage — new `TestSearchEntitiesSeededAreasIssue1170` class exercises the area_filter branches against a multi-domain populated area at the scale of the `tests/initial_test_state` seed, closing the gap noted in the triage. Tests - 5 new unit tests (`tests/src/unit/test_bm25_search.py::TestFuzzySearcherIssue1170`) - 8 new E2E tests (`tests/src/e2e/tools/test_search_entities.py`) - 1 new E2E test class with 3 test methods for finding 10 - 2 new regression tests for hidden_by filter in test_search_fallback.py - Updated test_search_fallback / test_search_pagination to drop references to the deleted `_partial_results_search`. Public API impact - Adds: `include_hidden` parameter; `area_names` field on area_only; `match_type="alias_match"` value. - Behavior change: `domain_filter` is normalized; hidden entities require explicit opt-in; area_only now includes `score`+`match_type`; `area_filtered_query` per-result `area_filter` echo dropped; `search_type="partial_listing"` no longer returned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Propagate get_states() exceptions in 3 fetch sites instead of silently emptying — auth/connection errors now surface rather than being masked as "zero matches" with success=true. - Fix alias_match label: subtract entity_id+friendly_name tokens from alias_hit so a query token present in BOTH name and alias doesn't mislabel as alias_match. - Move alias batch-fetch out of get_entities_by_area into tools_search.py's area+query branch — get_entities_by_area is exposed via server.py, so injecting `_aliases` was leaking an internal field through any caller round-tripping the response. - Tighten alias-fetch except clause to (KeyError, TypeError, AttributeError) so unexpected errors propagate while malformed payloads still degrade gracefully. - Sort area_filtered_query iteration order to match area_only. - Emit `area_names: []` on the empty-area-match branch for response-shape symmetry with the populated branch. - Document case-insensitive domain_filter in the Field description. - Tighten _strip_separators docstring + _exact_match_search docstring (no longer pure — also queries entity registry). - Soften "preserved for one minor version" comment to "kept for backward compatibility" — no removal deadline is committed. - Add unit test: alias-vs-friendly_name precedence (when query token matches both, name match wins, not alias_match). - Add e2e test: fuzzy-mode hidden filter (the existing test only exercised exact_match=True; fuzzy is a separate code path). - Add e2e test: area_only branch hidden filter + include_hidden=true opt-in. - Add e2e test: total_matches in area_only aggregates across all matched areas (locks down finding-7 fix at the pagination metadata layer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses 10 specific behavioral findings in the ha_search_entities tool, significantly improving search accuracy, API consistency, and the handling of hidden entities. It introduces robust filtering, better alias support, and cleaner fallback mechanisms, while also expanding E2E test coverage to ensure future stability. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request enhances the entity search system by introducing support for hidden entities and aliases, normalizing domain filters, and improving area-based matching. Key updates include parallelizing registry fetches, implementing a multi-token coverage gate in fuzzy matching to reduce noise, and removing the legacy partial results fallback. Review feedback focuses on ensuring that docstrings for both public tools and internal helpers start with approved action verbs to maintain consistency with project documentation standards.
Three docstrings now lead with an approved action verb per .gemini/styleguide.md and AGENTS.md: - smart_search.py:smart_entity_search — "Advanced entity search" → "Search entities..." - tools_search.py:_exact_match_search — "Substring search across..." → "Search entities by substring..." - fuzzy_search.py:_strip_separators — "Lowercase ``text``..." → "Strip ``.``, ``_``, ``-``..." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add `except ToolError: raise` guard in fuzzy fallback wrapper so auth/connection failures from the service layer propagate instead of being silently retried via _exact_match_search. - Lowercase domain_filter at the service-layer boundary too, so internal callers of SmartSearchTools.smart_entity_search get the same normalization the tool layer applies. - Log alias-enrichment failures with a structured `alias_enrichment_failed` prefix and survivor count, including the previously-silent case where send_websocket_message returns success=False. - Strip task-shaped `(homeassistant-ai#1170 finding N)` / `(closes homeassistant-ai#1166)` parentheticals from production-code comments and docstrings; technical why prose stays. - Drop two paraphrasing comments that restated the code below them. - Tests: tighten single-token typo assertion to require a `light.*` result, lock down `partial_id` precedence over `alias_match` when the query token also lives in id/name, assert area_only ordering is deterministic across calls, propagation of get_states failures as ToolError, plus E2E coverage for area-registry alias resolution and separator-elided concat-token queries. - seeded_bedroom fixture cleanup wrapped in try/finally so a fixture- body failure (or pytest.skip) doesn't leak the seed area assignments.
Hidden entities now surface in results with a 20-point score penalty applied whenever hidden_by is non-None — the option (c) approach from issue homeassistant-ai#1170 finding 9, in line with the issue's explicit menu of choices. Visible matches sort above hidden ones at comparable raw scores; agents that need to see hidden infrastructure entities still get them, just lower in the list. - New apply_hidden_penalty(score, hidden_by) helper in fuzzy_search.py; wired through every branch that emits a score (BM25, typo_fallback, exact_match, area_only, area_filtered_query, domain_listing). - include_hidden default flips True → callers keep the explicit opt-out (False) for visible-only search. - get_entities_by_area carries _hidden_by through entity dicts so the area_filtered_query and area_only branches can apply the penalty without a second registry lookup. - area_only and domain_listing now sort the result list by score so visible matches outrank penalised hidden peers within the same area/domain. - Tests rewritten: test_search_excludes_hidden_by_default → includes-with- penalty; new TestHiddenScorePenalty class covers helper math, ordering, and zero-clamp boundary; existing include_hidden=True opt-in test becomes include_hidden=False filter assertion.
Critical:
- BM25 + typo_fallback now gate the threshold on the *raw* score and
apply the hidden-score penalty only afterwards. Previously a hidden
entity at raw threshold (60 default, 80 in the area+query searcher)
was penalised below threshold and silently dropped — partially
regressing to option (b) for borderline matches and breaking the
option (c) "still surface, just rank lower" contract.
Important:
- server.py:get_entities_by_area bridge now strips internal
leading-underscore fields (`_hidden_by`, `_aliases`) via the new
strip_internal_fields helper so they don't leak to MCP clients.
- All three coerce_bool_param calls in ha_search_entities now sit
inside the try/except block. A bad string ("maybe") was previously
raising ValueError that escaped the structured exception handler
and surfaced as INTERNAL_ERROR.
- _exact_match_search and the domain_listing branch now log
hidden_filter_unavailable: when the registry/list call returns
non-success, mirroring the alias_enrichment_failed: pattern. Without
this an operator can't correlate "diagnostic entity ranking first"
with a transient WS hiccup.
Suggestions:
- apply_hidden_penalty coerces score to int defensively so a stray
float caller can't break the result-dict's int score contract.
- New public_fields(d) helper centralises the "strip leading
underscore" convention for non-mutating call sites; area_only
branch now uses it.
- Split the dense area_only multi-WHY comment into two focused ones.
- Drop paraphrase first sentence at the area_filtered_query result
builder; keep the why-comment about the dropped per-result field.
Tests:
- TestHiddenScorePenalty gains test_hidden_borderline_match_still_surfaces
(locks down the threshold-on-raw-score fix) and
test_hidden_typo_fallback_penalised (per-branch coverage for the
typo_fallback path).
- New E2E test_search_area_filtered_query_penalises_hidden_issue_1170
covers the area_filter+query path's _hidden_by plumbing through
get_entities_by_area → entities_for_search → BM25.
- New E2E test_search_domain_listing_penalises_hidden_issue_1170
covers the empty-query+domain_filter path's penalty + sort and
the include_hidden=False filter.
|
/Gemini review |
… Fork-Dev dev106 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request significantly enhances the search capabilities of the MCP server by implementing alias support for entities and areas, and introducing a score-penalty system for hidden entities. It also fixes a bug where area searches only returned results from a single matched area and improves the typo-fallback logic to reduce noise. Feedback from the review focuses on improving the robustness of exception handling for Home Assistant API calls, ensuring docstrings adhere to the project's style guide, and adding secondary sort keys to guarantee deterministic result ordering.
Stress test against the user's real HA caught a domain_filter bug: " LIGHT " was returning 0 results silently because lowercase normalization didn't strip whitespace first. Now both the tool layer (ha_search_entities) and the service layer (smart_entity_search) strip+lowercase, mirroring what was already done case-wise. Failing E2E test fixes: - test_search_area_filtered_query_penalises_hidden_issue_1170 was timing out for two reasons: missing ha_config_set_helper entity_id fallback (could be None when HA returns helper_data.id instead), and the no-separator helper name produced a single huge BM25 token that the prefix-only query couldn't fuzzy-match. Now uses a distinctive token in a space-separated name so BM25 hits at score 100, and falls back to helper_data.id when entity_id is absent. - test_search_domain_listing_penalises_hidden_issue_1170 had the same entity_id-fallback bug; same fix.
Gemini Code Assist:
- Tool docstring opens with "Search" instead of "Find or list"
(latter not on the approved-verb list).
- Sort tie-breaker on entity_id wherever the sort key was score-only
(fuzzy_search BM25 path, _exact_match_search, area_only, and the
domain_listing scored_entities sort). Without a stable secondary
key, paginated requests could shift the within-tier order between
calls — common with the hidden-penalty banding (visible@100,
hidden@80) and BM25's coarse buckets.
Silent-failure findings:
- Re-raise asyncio.CancelledError when it surfaces as a captured
exception from gather(return_exceptions=True). Previously the
`else` branch would log "hidden_filter_unavailable: ... CancelledError"
and continue, leaving the canceller waiting. Three call sites:
smart_entity_search, _exact_match_search, and the empty-query
domain-listing path.
- domain_filter / area_filter strip+lowercase now happens BEFORE the
at-least-one-set validation. Previously a whitespace-only filter
(" ") passed validation truthy then collapsed to "" and fell
through to a silent zero-result fuzzy search.
- strip_internal_fields now carries an _seen-set cycle guard so a
future caller feeding it a non-tree structure gets a clean
short-circuit instead of RecursionError.
Comment cleanup:
- Drop "option-c contract" task-shaped refs from fuzzy_search.py
comments; the WHY prose stands alone without the triage label.
- Tighten public_fields docstring: drop the parenthetical example
(one call site at the moment) and document the shallow-copy
contract so future callers don't trip on shared list values.
Code-reviewer suggestions:
- Drop the dead `query_lower` parameter from _typo_fallback (private
method, no API to preserve).
- area_filter zero-match echo now uses the canonical (stripped) form
by virtue of normalisation moved to the tool entry point.
Tests:
- New TestStripInternalFields and TestPublicFields in
test_util_helpers_internal_strip.py — pin the leak-guard contract
end to end (recursion, cycles, non-string keys, mutation
semantics).
- New parametrised E2E test_domain_filter_whitespace_normalized
(5 padded variants) locks down the strip step that the stress
test caught was missing.
- New test_domain_filter_whitespace_only_rejected pins the
validation-order fix.
- TestFuzzySearcherIssue1170::test_hidden_borderline_raw_score_threshold_edge
constructs a single-token single-doc corpus that lands at exactly
threshold=100 to lock down the post-gate penalty contract.
- test_search_area_filtered_query_penalises_hidden_issue_1170 escape
hatch removed: with the distinctive token now hitting BM25 at score
100, both helpers must surface unconditionally.
Round-4 pr-review-toolkit final pass flagged three coverage gaps that the prior rounds didn't already close: 1. `TestFuzzySearcherIssue1170::test_score_ties_break_on_entity_id_ascending` pins the new `(-score, entity_id)` sort tuple. Without an order assertion, a regression that drops the secondary key (or flips its direction) silently shifts pagination between calls. 2. `TestFuzzySearcherIssue1170::test_cancelled_error_propagates_from_registry_gather` and `TestExactMatchSearchCancelledPropagation::test_cancelled_on_registry_task_propagates` lock down the new asyncio.CancelledError re-raise after gather(return_exceptions=True). Pre-fix the captured cancellation hit the `hidden_filter_unavailable:` log and the function continued — the canceller would wait forever. 3. `test_server_bridge_strip.test_get_entities_by_area_bridge_strips_internal_fields` exercises the public bridge end-to-end with a mock smart_tools that returns a dict carrying `_hidden_by` / `_aliases`. If a future refactor deletes the `strip_internal_fields(result)` line in the bridge, internal fields would leak to MCP clients with no signal in CI — this test catches that.
ImportError: HASmartMCPServer doesn't exist — the actual class is HomeAssistantSmartMCPServer. Drop the unused monkeypatch fixture while I'm in there.
smart_tools is a lazy-init property with no setter (server.py:218); the test was hitting 'object has no setter'. Set the underscored backing field directly so the property short-circuits to the fake.
… Fork-Dev dev107 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude discovere the following things. Claude is being particularly stupid lately, so these might be nonsense, so confirm they are actually legitimate because there's a good chance that they aren't, but if they are then we should probably fix them. 1. Exact
Repro: assign If real: short-circuit when 2. Single-token sub-word noise in The coverage-gate fix from finding 5 is multi-token-only by construction. Single-token queries still surface unrelated entities via SequenceMatcher partial overlap. Original example from the issue: Repro: If real: either raise the typo_fallback threshold for single-token queries (~85), or require min query length ≥ 4 for the fallback to fire, in 3. Result-shape consistency was the target of finding 3, which closed the Repro: compare If real: decide which way to go, then either drop the field in 4. Misleading A Repro: If real: pass an appropriate suggestions list when constructing the validation error, or drop the suggestion field for validation errors entirely. The error originates from 5. When Repro: If real: emit a parallel |
1. Exact area_id short-circuits fuzzy aggregation. A query like area_filter='bedroom_kids' was partial_ratio-matching its parent 'bedroom' (score=100, clears the 80 threshold) and aggregating sibling areas' entities. Now exact id/name/alias matches suppress the fuzzy step entirely; fuzzy only fires when no exact hit exists. 2. Single-token typo_fallback min-length gate. The coverage gate from finding 5 was multi-token-only by construction: 'lit' (3 chars) still surfaced every '*_lite*' entity at score 85 via partial overlap. Short single-token queries now skip the fallback entirely; 4+ chars (typical real typos like 'ligth'→'light') are unaffected. 3. Result shape parity. fuzzy_search emitted an 'essential_attributes' dict that the other four branches (exact_match, area_only, area_filtered_query, domain_listing) never carried — a shape asymmetry that issue homeassistant-ai#1170 finding 3 didn't address. Dropped from fuzzy_search for consistency; callers needing full state should follow up with ha_get_state. 4. Validation errors no longer carry misleading generic suggestions. A 'limit=0' input would surface as VALIDATION_FAILED with message 'limit must be at least 1, got 0' but suggestions like 'Check Home Assistant connection' — boilerplate from the generic exception handler. ValueError from coerce_*_param is now caught separately and surfaced via create_validation_error with the helper's own message and no operational suggestions. 5. area_filter+domain_filter zero-overlap now emits a message. When areas resolve but the domain_filter wipes out every entity in them, the response carries 'No <domain> entities found in area: <area>' instead of returning total_matches=0 silently. Test coverage: - TestFuzzySearcherIssue1170 gets test_typo_fallback_short_single_token_returns_empty and test_typo_fallback_four_char_single_token_still_fires for finding 2. - New E2E test_exact_area_id_short_circuits_fuzzy_aggregation pins finding 1 against the two_areas_with_shared_prefix fixture. - New E2E test_result_shape_consistent_across_branches loops all 5 search_types and asserts base-keys-present plus essential_attributes-absent. - New E2E test_validation_error_carries_no_generic_suggestions hits limit=0 and asserts the leaker strings are absent. - New E2E test_area_filter_with_domain_filter_zero_overlap_has_message hits area_filter=kitchen + domain_filter=zone (never assigned per-area) and asserts the new message field.
… Fork-Dev dev108 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implementation SummaryChoices Made:
Problems Encountered:
|
Patch76
left a comment
There was a problem hiding this comment.
Triage-comprehensive coverage of #1170 — all 10 findings closed, plus the alias work folding in #1166.
Verified G4/G6 dismissals: client.send_websocket_message (rest_client.py:1101-1129) catches except Exception broadly and returns a {success, error} dict, so HomeAssistantConnectionError / HomeAssistantAPIError / TimeoutError / OSError don't propagate to either alias-enrichment site. The narrow (KeyError, TypeError, AttributeError) catch correctly scopes to malformed-but-success payloads; transport failures hit the explicit non-success else branch that logs alias_enrichment_failed: with the survivor count. Reasoning checks out.
Backwards-compat on Finding 7: tools_search.py:494-503 emits both area_names: list[str] (new) and area_name: str (legacy, first match by sorted area_id), with the soft-deprecation flagged in-line. Not breaking per the styleguide's return-restructuring rule.
Test envelope: +1839 net test lines — one E2E file (test_search_entities.py), two new unit files (test_server_bridge_strip.py, test_util_helpers_internal_strip.py), a BM25 expansion (test_bm25_search.py, +462 net), plus rewrites of test_search_fallback and test_search_pagination. Both new helpers (strip_internal_fields + public_fields) ship with dedicated unit coverage.
|
LGTM |
🧪 Your changes are now in the dev channel!Your PR has been merged to master and is available for testing in the dev channel. Test your changes before the next stable release (biweekly Wednesday): Quick start# Run dev version
uvx ha-mcp-dev
# Check version
uvx ha-mcp-dev --versionDocker: docker pull ghcr.io/homeassistant-ai/ha-mcp:dev
docker run --rm -i \
-e HOMEASSISTANT_URL=http://your-ha:8123 \
-e HOMEASSISTANT_TOKEN=your_token \
ghcr.io/homeassistant-ai/ha-mcp:devFound an issue? Please open a new bug report and mention this PR for context. |
…→ 7.5.0) (#455) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/homeassistant-ai/ha-mcp](https://github.com/homeassistant-ai/ha-mcp) | minor | `7.4.0` → `7.5.0` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/3) for more information. --- ### Release Notes <details> <summary>homeassistant-ai/ha-mcp (ghcr.io/homeassistant-ai/ha-mcp)</summary> ### [`v7.5.0`](https://github.com/homeassistant-ai/ha-mcp/blob/HEAD/CHANGELOG.md#v750-2026-05-13) [Compare Source](homeassistant-ai/ha-mcp@v7.4.0...v7.5.0) ##### Added - Add ENABLE\_LITE\_DOCSTRINGS beta toggle ([#​1259](homeassistant-ai/ha-mcp#1259)) - Add ha\_call\_event tool for publishing events on the HA event bus ([#​996](homeassistant-ai/ha-mcp#996)) ([#​1239](homeassistant-ai/ha-mcp#1239)) - Pinpoint backslash-escape mistake in python\_sandbox errors ([#​1204](homeassistant-ai/ha-mcp#1204)) - Reject empty-trigger automations targeting scene.create ([#​1187](homeassistant-ai/ha-mcp#1187)) - Add scene config tools — ha\_config\_get/set/remove\_scene ([#​1168](homeassistant-ai/ha-mcp#1168)) - **addon**: Optional OAuth 2.1 mode for webhook proxy (beta) ([#​1184](homeassistant-ai/ha-mcp#1184)) - Surface helper schema inline in ha\_config\_set\_helper validation errors ([#​1149](homeassistant-ai/ha-mcp#1149)) ([#​1179](homeassistant-ai/ha-mcp#1179)) - Emit progress via FastMCP Context in long-running tools ([#​1124](homeassistant-ai/ha-mcp#1124)) - Broaden python\_transform AST allowlist + improve error UX ([#​1163](homeassistant-ai/ha-mcp#1163)) - Add ha\_manage\_custom\_tool — sandboxed code execution escape hatch ([#​854](homeassistant-ai/ha-mcp#854)) - Always-on skills; rename list/read resource tools with ha\_ prefix ([#​1136](homeassistant-ai/ha-mcp#1136)) - Expose device\_class + options on ha\_set\_entity / ha\_get\_entity (Show As) ([#​1135](homeassistant-ai/ha-mcp#1135)) - **site**: Inline wizard data into setup.astro, migrate setup nuggets, drop content collections ([#​1120](homeassistant-ai/ha-mcp#1120)) - Add "Advanced debug logging" toggle for kill-signal diagnostics ([#​1117](homeassistant-ai/ha-mcp#1117)) - **yaml**: Scoped lovelace.dashboards.\<url\_path> support (issue [#​1034](homeassistant-ai/ha-mcp#1034)) ([#​1103](homeassistant-ai/ha-mcp#1103)) - Add HA\_VERIFY\_SSL toggle to disable TLS verification ([#​1104](homeassistant-ai/ha-mcp#1104)) - Per-top-level-key config\_hash for ha\_manage\_energy\_prefs ([#​1049](homeassistant-ai/ha-mcp#1049)) ([#​1098](homeassistant-ai/ha-mcp#1098)) - **site**: Add gemini-cli setup notes + compose hardening to wizard ([#​1027](homeassistant-ai/ha-mcp#1027)) ([#​1087](homeassistant-ai/ha-mcp#1087)) - Add convenience modes to ha\_manage\_energy\_prefs ([#​1050](homeassistant-ai/ha-mcp#1050)) ([#​1073](homeassistant-ai/ha-mcp#1073)) - Surface integration log levels in ha\_get\_logs/integration/addon ([#​956](homeassistant-ai/ha-mcp#956)) ([#​1003](homeassistant-ai/ha-mcp#1003)) - Expose allowlist\_external\_dirs in ha\_get\_overview full system\_info ([#​1053](homeassistant-ai/ha-mcp#1053)) - **dashboards**: Unify identifier handling in ha\_config\_\*\_dashboard tools ([#​981](homeassistant-ai/ha-mcp#981)) ([#​1075](homeassistant-ai/ha-mcp#1075)) - Include addon container logs in bug reports ([#​934](homeassistant-ai/ha-mcp#934)) - Add WebSocket response-shaping controls to ha\_manage\_addon ([#​1009](homeassistant-ai/ha-mcp#1009)) - Web-based settings UI for per-tool enable/disable/pin ([#​960](homeassistant-ai/ha-mcp#960)) - **site**: Add OpenCode support to setup wizard ([#​1080](homeassistant-ai/ha-mcp#1080)) ##### Changed - Clarify standard-mode HTTP deployment guidance ([#​1185](homeassistant-ai/ha-mcp#1185)) - Add Cloudflared add-on hostname alternative for tunnel service ([#​1183](homeassistant-ai/ha-mcp#1183)) - Align tool naming convention between AGENTS.md and styleguide ([#​943](homeassistant-ai/ha-mcp#943)) ([#​1174](homeassistant-ai/ha-mcp#1174)) - **addon**: Note tool-list ([#​985](homeassistant-ai/ha-mcp#985 divergence; fix [#​1139](https://github.com/homeassistant-ai/ha-mcp/issues/1139)/[#​1162](https://github.com/homeassistant-ai/ha-mcp/issues/1162) test conflict ([#​1172](homeassistant-ai/ha-mcp#1172)) - Add brew install option for mcp-proxy on macOS ([#​1171](homeassistant-ai/ha-mcp#1171)) - Update contributors list \[contributors-updated] ([`aba01a1`](homeassistant-ai/ha-mcp@aba01a1)) - Warn against enable\_tool\_search on Claude Sonnet/Opus ([#​1088](homeassistant-ai/ha-mcp#1088)) ([#​1140](homeassistant-ai/ha-mcp#1140)) - Address [#​1094](homeassistant-ai/ha-mcp#1094) review nits on OpenCode mirror comments ([#​1105](homeassistant-ai/ha-mcp#1105)) ##### Fixed - **integrations**: Surface ConfigEntry.options via OptionsFlow probe ([#​1245](homeassistant-ai/ha-mcp#1245)) - **backup**: Discover local agent at call time instead of hardcoding hassio.local ([#​1244](homeassistant-ai/ha-mcp#1244)) - Triage all 10 ha\_search\_entities behaviors from [#​1170](homeassistant-ai/ha-mcp#1170) ([#​1195](homeassistant-ai/ha-mcp#1195)) - Replace cron with systemd for demo server (prevents process leak) ([#​1110](homeassistant-ai/ha-mcp#1110)) - Improve ha\_manage\_addon discoverability (BM25 keywords + slug examples) ([#​1200](homeassistant-ai/ha-mcp#1200)) - Route Supervisor 401s to structured tool errors + add E2E coverage ([#​1129](homeassistant-ai/ha-mcp#1129)) ([#​1192](homeassistant-ai/ha-mcp#1192)) - Harden \_validate\_category\_id gate to cover dict-promoted category ([#​1190](homeassistant-ai/ha-mcp#1190)) - Broaden template anti-pattern detection + skill discoverability ([#​1011](homeassistant-ai/ha-mcp#1011)) ([#​1181](homeassistant-ai/ha-mcp#1181)) - Return newest automation traces, add offset+order pagination ([#​1177](homeassistant-ai/ha-mcp#1177)) ([#​1178](homeassistant-ai/ha-mcp#1178)) - **security**: Write YAML backups outside www/ (GHSA-g39v-cvjh-8fpf) ([#​1180](homeassistant-ai/ha-mcp#1180)) - **search**: Apply domain\_filter when area\_filter is set ([#​1162](homeassistant-ai/ha-mcp#1162)) ([#​1165](homeassistant-ai/ha-mcp#1165)) - **resources**: Reject HA-config YAML in dashboard resource content ([#​1160](homeassistant-ai/ha-mcp#1160)) - Close 19 bugs in ha\_config\_set\_helper (issue [#​1150](homeassistant-ai/ha-mcp#1150)) ([#​1151](homeassistant-ai/ha-mcp#1151)) - Route addon log fetches directly to supervisor on addon installs ([#​1126](homeassistant-ai/ha-mcp#1126)) - Survive read-only filesystems at startup ([#​1138](homeassistant-ai/ha-mcp#1138)) - **helpers**: Clarify name-required-on-create for ha\_config\_set\_helper ([#​1143](homeassistant-ai/ha-mcp#1143)) - Resolve disabled entities via entity\_registry in helper deletion ([#​1119](homeassistant-ai/ha-mcp#1119)) - Allow unary operators in python\_transform sandbox ([#​1118](homeassistant-ai/ha-mcp#1118)) - **site**: Add github-copilot-agents wizard branch + delete unreferenced data/clients.ts ([#​1108](homeassistant-ai/ha-mcp#1108)) - **addons**: Route addon API calls through HA Core ingress proxy ([#​1069](homeassistant-ai/ha-mcp#1069)) - **webhook-proxy**: Surface webhook registration failures instead of silently loading ([#​1101](homeassistant-ai/ha-mcp#1101)) - **site**: Resolve client display-order collisions and anchor OpenCode shape ([#​1094](homeassistant-ai/ha-mcp#1094)) ##### Performance Improvements - Dedupe lovelace/dashboards/list in ha\_config\_set\_dashboard ([#​1085](homeassistant-ai/ha-mcp#1085)) ([#​1191](homeassistant-ai/ha-mcp#1191)) ##### Refactoring - Drop obsolete ha\_mcp\_tools defensive ruamel.yaml imports ([post-#​1268](https://github.com/post-/ha-mcp/issues/1268)) ([#​1269](homeassistant-ai/ha-mcp#1269)) - Extract shared Supervisor httpx client helper ([#​1130](homeassistant-ai/ha-mcp#1130)) ([#​1203](homeassistant-ai/ha-mcp#1203)) - Surface client identity, AI model, config toggles, and prompt context in ha\_report\_issue ([#​1189](homeassistant-ai/ha-mcp#1189)) - Harden Context injection with safe-emit + branch coverage ([#​1173](homeassistant-ai/ha-mcp#1173)) - Consolidate area/floor set+remove tools (revisit of [#​813](homeassistant-ai/ha-mcp#813)) ([#​1139](homeassistant-ai/ha-mcp#1139)) - Pass verify\_ssl to remaining direct-Supervisor httpx callers ([#​1128](homeassistant-ai/ha-mcp#1128)) - Validate only new entries on convenience-mode writes ([#​1086](homeassistant-ai/ha-mcp#1086)) ([#​1100](homeassistant-ai/ha-mcp#1100)) *** <details> <summary>Internal Changes</summary> ##### Fixed - **ci**: Align pr.yml E2E with --dist loadscope ([#​1206](homeassistant-ai/ha-mcp#1206)) ([#​1247](homeassistant-ai/ha-mcp#1247)) - **ci**: Switch Renovate to a GitHub App token to allow workflow-file pushes ([#​1229](homeassistant-ai/ha-mcp#1229)) - **ci**: Break gemini-triage retrigger loop and bump turn budget ([#​1131](homeassistant-ai/ha-mcp#1131)) - **ci**: Harden gemini-triage so failures stop spamming user issues ([#​1122](homeassistant-ai/ha-mcp#1122)) - **ci**: Unbreak hotfix-release semantic-release run ([#​1091](homeassistant-ai/ha-mcp#1091)) ##### Chores - **addon**: Publish dev addon version 7.4.1.dev299 \[skip ci] ([`397aa6d`](homeassistant-ai/ha-mcp@397aa6d)) - **addon**: Publish dev addon version 7.4.1.dev298 \[skip ci] ([`942b7e0`](homeassistant-ai/ha-mcp@942b7e0)) - Sync tool docs after merge \[skip ci] ([`6823c47`](homeassistant-ai/ha-mcp@6823c47)) - **addon**: Publish dev addon version 7.4.1.dev297 \[skip ci] ([`6eac062`](homeassistant-ai/ha-mcp@6eac062)) - **addon**: Publish dev addon version 7.4.1.dev296 \[skip ci] ([`b2afe93`](homeassistant-ai/ha-mcp@b2afe93)) - **addon**: Publish dev addon version 7.4.1.dev295 \[skip ci] ([`4f4c4f3`](homeassistant-ai/ha-mcp@4f4c4f3)) - **deps**: Update ghcr.io/home-assistant/home-assistant docker tag to v2026.5.1 ([#​1236](homeassistant-ai/ha-mcp#1236)) - **addon**: Publish dev addon version 7.4.1.dev294 \[skip ci] ([`fd24991`](homeassistant-ai/ha-mcp@fd24991)) - **deps**: Update ghcr.io/astral-sh/uv docker tag to v0.11.13 ([#​1233](homeassistant-ai/ha-mcp#1233)) - **addon**: Publish dev addon version 7.4.1.dev293 \[skip ci] ([`fcc6496`](homeassistant-ai/ha-mcp@fcc6496)) - **addon**: Publish dev addon version 7.4.1.dev292 \[skip ci] ([`2961650`](homeassistant-ai/ha-mcp@2961650)) - **addon**: Publish dev addon version 7.4.1.dev291 \[skip ci] ([`5703112`](homeassistant-ai/ha-mcp@5703112)) - **addon**: Publish dev addon version 7.4.1.dev290 \[skip ci] ([`19b2f65`](homeassistant-ai/ha-mcp@19b2f65)) - **addon**: Publish dev addon version 7.4.1.dev289 \[skip ci] ([`e5a1365`](homeassistant-ai/ha-mcp@e5a1365)) - Sync tool docs after merge \[skip ci] ([`d2ff93b`](homeassistant-ai/ha-mcp@d2ff93b)) - **addon**: Publish dev addon version 7.4.1.dev288 \[skip ci] ([`0f62400`](homeassistant-ai/ha-mcp@0f62400)) - Sync tool docs after merge \[skip ci] ([`c7e2066`](homeassistant-ai/ha-mcp@c7e2066)) - **addon**: Publish dev addon version 7.4.1.dev287 \[skip ci] ([`c1133d4`](homeassistant-ai/ha-mcp@c1133d4)) - **addon**: Publish dev addon version 7.4.1.dev286 \[skip ci] ([`1ae790e`](homeassistant-ai/ha-mcp@1ae790e)) - **addon**: Publish dev addon version 7.4.1.dev285 \[skip ci] ([`2387d0c`](homeassistant-ai/ha-mcp@2387d0c)) - **addon**: Publish dev addon version 7.4.1.dev284 \[skip ci] ([`dd3a4a5`](homeassistant-ai/ha-mcp@dd3a4a5)) - **addon**: Publish dev addon version 7.4.1.dev283 \[skip ci] ([`78af8eb`](homeassistant-ai/ha-mcp@78af8eb)) - Sync tool docs after merge \[skip ci] ([`093fd74`](homeassistant-ai/ha-mcp@093fd74)) - **addon**: Publish dev addon version 7.4.1.dev282 \[skip ci] ([`2141e15`](homeassistant-ai/ha-mcp@2141e15)) - Sync tool docs after merge \[skip ci] ([`7810c95`](homeassistant-ai/ha-mcp@7810c95)) - **addon**: Publish dev addon version 7.4.1.dev281 \[skip ci] ([`7d79ec2`](homeassistant-ai/ha-mcp@7d79ec2)) - Sync tool docs after merge \[skip ci] ([`a73dc81`](homeassistant-ai/ha-mcp@a73dc81)) - **addon**: Publish dev addon version 7.4.1.dev280 \[skip ci] ([`c858ce3`](homeassistant-ai/ha-mcp@c858ce3)) - Sync tool docs after merge \[skip ci] ([`a587be0`](homeassistant-ai/ha-mcp@a587be0)) - **addon**: Publish dev addon version 7.4.1.dev279 \[skip ci] ([`b78ddb2`](homeassistant-ai/ha-mcp@b78ddb2)) - Sync tool docs after merge \[skip ci] ([`1210725`](homeassistant-ai/ha-mcp@1210725)) - **addon**: Publish dev addon version 7.4.1.dev278 \[skip ci] ([`a282c17`](homeassistant-ai/ha-mcp@a282c17)) - **addon**: Publish dev addon version 7.4.1.dev277 \[skip ci] ([`1081768`](homeassistant-ai/ha-mcp@1081768)) - Sync tool docs after merge \[skip ci] ([`e03f5d2`](homeassistant-ai/ha-mcp@e03f5d2)) - **addon**: Publish dev addon version 7.4.1.dev276 \[skip ci] ([`c4ef680`](homeassistant-ai/ha-mcp@c4ef680)) - **addon**: Publish dev addon version 7.4.1.dev275 \[skip ci] ([`780422d`](homeassistant-ai/ha-mcp@780422d)) - Sync tool docs after merge \[skip ci] ([`8a2bd1a`](homeassistant-ai/ha-mcp@8a2bd1a)) - **addon**: Publish dev addon version 7.4.1.dev274 \[skip ci] ([`f0f09de`](homeassistant-ai/ha-mcp@f0f09de)) - **addon**: Publish dev addon version 7.4.1.dev273 \[skip ci] ([`cb49f68`](homeassistant-ai/ha-mcp@cb49f68)) - **addon**: Publish dev addon version 7.4.1.dev272 \[skip ci] ([`5097186`](homeassistant-ai/ha-mcp@5097186)) - **addon**: Publish dev addon version 7.4.1.dev271 \[skip ci] ([`4714342`](homeassistant-ai/ha-mcp@4714342)) - **addon**: Publish dev addon version 7.4.1.dev270 \[skip ci] ([`217982a`](homeassistant-ai/ha-mcp@217982a)) - **addon**: Publish dev addon version 7.4.1.dev269 \[skip ci] ([`a65dd5f`](homeassistant-ai/ha-mcp@a65dd5f)) - Sync tool docs after merge \[skip ci] ([`0e6b54f`](homeassistant-ai/ha-mcp@0e6b54f)) - **addon**: Publish dev addon version 7.4.1.dev268 \[skip ci] ([`60ba1f2`](homeassistant-ai/ha-mcp@60ba1f2)) - **addon**: Publish dev addon version 7.4.1.dev267 \[skip ci] ([`13412aa`](homeassistant-ai/ha-mcp@13412aa)) - Sync tool docs after merge \[skip ci] ([`2702a0f`](homeassistant-ai/ha-mcp@2702a0f)) - **addon**: Publish dev addon version 7.4.1.dev266 \[skip ci] ([`77abe0b`](homeassistant-ai/ha-mcp@77abe0b)) - **addon**: Publish dev addon version 7.4.1.dev265 \[skip ci] ([`08b69db`](homeassistant-ai/ha-mcp@08b69db)) - Sync tool docs after merge \[skip ci] ([`c1f24b5`](homeassistant-ai/ha-mcp@c1f24b5)) - **addon**: Publish dev addon version 7.4.1.dev264 \[skip ci] ([`f2583f6`](homeassistant-ai/ha-mcp@f2583f6)) - Sync tool docs after merge \[skip ci] ([`c2ed2d3`](homeassistant-ai/ha-mcp@c2ed2d3)) - **addon**: Publish dev addon version 7.4.1.dev263 \[skip ci] ([`9d43e54`](homeassistant-ai/ha-mcp@9d43e54)) - **addon**: Publish dev addon version 7.4.1.dev262 \[skip ci] ([`a7355c8`](homeassistant-ai/ha-mcp@a7355c8)) - Sync tool docs after merge \[skip ci] ([`085bd8a`](homeassistant-ai/ha-mcp@085bd8a)) - Convert agents to skills ([#​1084](homeassistant-ai/ha-mcp#1084)) - **addon**: Publish dev addon version 7.4.1.dev261 \[skip ci] ([`0d1af36`](homeassistant-ai/ha-mcp@0d1af36)) - **addon**: Publish dev addon version 7.4.1.dev260 \[skip ci] ([`29397dc`](homeassistant-ai/ha-mcp@29397dc)) - **addon**: Publish dev addon version 7.4.1.dev259 \[skip ci] ([`4bbc74b`](homeassistant-ai/ha-mcp@4bbc74b)) - Sync tool docs after merge \[skip ci] ([`0f6d41e`](homeassistant-ai/ha-mcp@0f6d41e)) - **addon**: Publish dev addon version 7.4.1.dev258 \[skip ci] ([`6751d08`](homeassistant-ai/ha-mcp@6751d08)) - **addon**: Publish dev addon version 7.4.1.dev257 \[skip ci] ([`2213c89`](homeassistant-ai/ha-mcp@2213c89)) - **addon**: Publish dev addon version 7.4.1.dev256 \[skip ci] ([`18a366e`](homeassistant-ai/ha-mcp@18a366e)) - **addon**: Publish dev addon version 7.4.1.dev255 \[skip ci] ([`0e9b18d`](homeassistant-ai/ha-mcp@0e9b18d)) - **addon**: Publish dev addon version 7.4.1.dev254 \[skip ci] ([`39fc65b`](homeassistant-ai/ha-mcp@39fc65b)) - Sync tool docs after merge \[skip ci] ([`9fa0aea`](homeassistant-ai/ha-mcp@9fa0aea)) - **addon**: Publish dev addon version 7.4.1.dev253 \[skip ci] ([`0dcc59e`](homeassistant-ai/ha-mcp@0dcc59e)) - Sync tool docs after merge \[skip ci] ([`ec7413f`](homeassistant-ai/ha-mcp@ec7413f)) - **addon**: Publish dev addon version 7.4.1.dev252 \[skip ci] ([`345640c`](homeassistant-ai/ha-mcp@345640c)) - **addon**: Publish dev addon version 7.4.1.dev251 \[skip ci] ([`bab9d49`](homeassistant-ai/ha-mcp@bab9d49)) - Sync tool docs after merge \[skip ci] ([`726f0a5`](homeassistant-ai/ha-mcp@726f0a5)) - **addon**: Publish dev addon version 7.4.1.dev250 \[skip ci] ([`ded04ea`](homeassistant-ai/ha-mcp@ded04ea)) - **addon**: Publish dev addon version 7.4.1.dev249 \[skip ci] ([`37d5628`](homeassistant-ai/ha-mcp@37d5628)) - **addon**: Publish dev addon version 7.4.1.dev248 \[skip ci] ([`530786a`](homeassistant-ai/ha-mcp@530786a)) - Sync tool docs after merge \[skip ci] ([`36719c3`](homeassistant-ai/ha-mcp@36719c3)) - **addon**: Publish dev addon version 7.4.1.dev247 \[skip ci] ([`4dc47b5`](homeassistant-ai/ha-mcp@4dc47b5)) - **addon**: Publish dev addon version 7.4.1.dev246 \[skip ci] ([`6ffbd6a`](homeassistant-ai/ha-mcp@6ffbd6a)) - Sync tool docs after merge \[skip ci] ([`add66e3`](homeassistant-ai/ha-mcp@add66e3)) - **addon**: Publish dev addon version 7.4.1.dev245 \[skip ci] ([`d0114af`](homeassistant-ai/ha-mcp@d0114af)) - Sync tool docs after merge \[skip ci] ([`0ca41af`](homeassistant-ai/ha-mcp@0ca41af)) - **addon**: Publish dev addon version 7.4.1.dev244 \[skip ci] ([`d052dd0`](homeassistant-ai/ha-mcp@d052dd0)) - **addon**: Publish dev addon version 7.4.0.dev243 \[skip ci] ([`827bc65`](homeassistant-ai/ha-mcp@827bc65)) - Bump package version to 7.4.1 to match released addon ([`4f65497`](homeassistant-ai/ha-mcp@4f65497)) - **addon**: Publish dev addon version 7.4.0.dev242 \[skip ci] ([`8ba80ae`](homeassistant-ai/ha-mcp@8ba80ae)) - **addon**: Publish hotfix version 7.4.1 ([`bda75e6`](homeassistant-ai/ha-mcp@bda75e6)) - **addon**: Publish dev addon version 7.4.0.dev241 \[skip ci] ([`2126428`](homeassistant-ai/ha-mcp@2126428)) ##### Continuous Integration - **deps**: Bump renovatebot/github-action in the github-actions group ([#​1218](homeassistant-ai/ha-mcp#1218)) - **deps**: Bump renovatebot/github-action in the github-actions group ([#​1111](homeassistant-ai/ha-mcp#1111)) ##### Refactoring - Extract \_fetch\_dashboards\_list helper ([#​1193](homeassistant-ai/ha-mcp#1193)) ([#​1207](homeassistant-ai/ha-mcp#1207)) ##### Testing - **e2e**: Module-scope bulk\_automations + bulk\_scripts fixtures (refs [#​366](homeassistant-ai/ha-mcp#366)) ([#​1275](homeassistant-ai/ha-mcp#1275)) - **e2e**: Lower INPUT\_BOOLEAN\_WAIT from 30s to 10s (refs [#​366](homeassistant-ai/ha-mcp#366)) ([#​1273](homeassistant-ai/ha-mcp#1273)) - **e2e**: Generalize readiness-gate diagnostics helper (closes [#​1267](homeassistant-ai/ha-mcp#1267)) ([#​1271](homeassistant-ai/ha-mcp#1271)) - **e2e**: Narrow except clauses in e2e polling helpers (closes [#​1266](homeassistant-ai/ha-mcp#1266)) ([#​1270](homeassistant-ai/ha-mcp#1270)) - **e2e**: Drop ha\_mcp\_tools retry-path + pre-install manifest requirements ([#​1268](homeassistant-ai/ha-mcp#1268)) - **e2e**: Instrument and retry ha\_mcp\_tools readiness wait ([#​1262](homeassistant-ai/ha-mcp#1262)) - Use time.monotonic() in UAT runner and test\_env\_manager ([#​1254](homeassistant-ai/ha-mcp#1254)) - **e2e**: Detect partial/corrupt hacs\_frontend dir in fast-path guard ([#​1253](homeassistant-ai/ha-mcp#1253)) - **e2e**: Remove unused wait/assert helpers ([post-#​1249](https://github.com/post-/ha-mcp/issues/1249) audit) ([#​1256](homeassistant-ai/ha-mcp#1256)) - **e2e**: Clear stale .hacs\_frontend.lock from prior crashed runs ([#​1252](homeassistant-ai/ha-mcp#1252)) - **e2e**: Use time.monotonic() in workflow polling loops ([#​1258](homeassistant-ai/ha-mcp#1258)) - **e2e**: Use time.monotonic() for duration polling ([#​1234](homeassistant-ai/ha-mcp#1234)) ([#​1249](homeassistant-ai/ha-mcp#1249)) - **e2e**: Close ARM ha\_mcp\_tools readiness race under loadscope ([#​1208](homeassistant-ai/ha-mcp#1208)) - **hacs**: Tighten is\_hacs\_unavailable to not match legitimate "Repository not found" ([#​1246](homeassistant-ai/ha-mcp#1246)) - **seed**: Unblock 3 silent-skip pagination/state tests via baked recorder DB ([#​1240](homeassistant-ai/ha-mcp#1240)) - **seed**: Register a writable local\_calendar to unblock event-creation test ([#​1243](homeassistant-ai/ha-mcp#1243)) - **addon**: Fix base64 padding-bit flake in token tamper tests ([#​1238](homeassistant-ai/ha-mcp#1238)) ([#​1241](homeassistant-ai/ha-mcp#1241)) - **seed**: Add a writable scene for test\_call\_service\_scene\_turn\_on ([#​1231](homeassistant-ai/ha-mcp#1231)) - **seed**: Assign demo device to living\_room area for filter test ([#​1230](homeassistant-ai/ha-mcp#1230)) - **e2e**: Drop nonexistent sun service from session readiness wait ([#​1227](homeassistant-ai/ha-mcp#1227)) - **e2e**: Self-contain dashboard register/remove to fix ARM xdist race ([#​1196](homeassistant-ai/ha-mcp#1196)) ([#​1201](homeassistant-ai/ha-mcp#1201)) - Fix EN dash in docstring causing RUF002 lint failure ([`eac5916`](homeassistant-ai/ha-mcp@eac5916)) - Address Gemini review feedback on host detection and port allocation ([`960305e`](homeassistant-ai/ha-mcp@960305e)) - Fix three categories of E2E test flakiness ([`39417ff`](homeassistant-ai/ha-mcp@39417ff)) - **e2e**: Pin config\_hash stability for dashboards ([#​1132](homeassistant-ai/ha-mcp#1132)) </details> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/455
….0 ) (#26) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/homeassistant-ai/ha-mcp](https://github.com/homeassistant-ai/ha-mcp) | minor | `7.4.0` → `7.5.0` | --- ### Release Notes <details> <summary>homeassistant-ai/ha-mcp (ghcr.io/homeassistant-ai/ha-mcp)</summary> ### [`v7.5.0`](https://github.com/homeassistant-ai/ha-mcp/blob/HEAD/CHANGELOG.md#v750-2026-05-13) [Compare Source](homeassistant-ai/ha-mcp@v7.4.0...v7.5.0) ##### Added - Add ENABLE\_LITE\_DOCSTRINGS beta toggle ([#​1259](homeassistant-ai/ha-mcp#1259)) - Add ha\_call\_event tool for publishing events on the HA event bus ([#​996](homeassistant-ai/ha-mcp#996)) ([#​1239](homeassistant-ai/ha-mcp#1239)) - Pinpoint backslash-escape mistake in python\_sandbox errors ([#​1204](homeassistant-ai/ha-mcp#1204)) - Reject empty-trigger automations targeting scene.create ([#​1187](homeassistant-ai/ha-mcp#1187)) - Add scene config tools — ha\_config\_get/set/remove\_scene ([#​1168](homeassistant-ai/ha-mcp#1168)) - **addon**: Optional OAuth 2.1 mode for webhook proxy (beta) ([#​1184](homeassistant-ai/ha-mcp#1184)) - Surface helper schema inline in ha\_config\_set\_helper validation errors ([#​1149](homeassistant-ai/ha-mcp#1149)) ([#​1179](homeassistant-ai/ha-mcp#1179)) - Emit progress via FastMCP Context in long-running tools ([#​1124](homeassistant-ai/ha-mcp#1124)) - Broaden python\_transform AST allowlist + improve error UX ([#​1163](homeassistant-ai/ha-mcp#1163)) - Add ha\_manage\_custom\_tool — sandboxed code execution escape hatch ([#​854](homeassistant-ai/ha-mcp#854)) - Always-on skills; rename list/read resource tools with ha\_ prefix ([#​1136](homeassistant-ai/ha-mcp#1136)) - Expose device\_class + options on ha\_set\_entity / ha\_get\_entity (Show As) ([#​1135](homeassistant-ai/ha-mcp#1135)) - **site**: Inline wizard data into setup.astro, migrate setup nuggets, drop content collections ([#​1120](homeassistant-ai/ha-mcp#1120)) - Add "Advanced debug logging" toggle for kill-signal diagnostics ([#​1117](homeassistant-ai/ha-mcp#1117)) - **yaml**: Scoped lovelace.dashboards.\<url\_path> support (issue [#​1034](homeassistant-ai/ha-mcp#1034)) ([#​1103](homeassistant-ai/ha-mcp#1103)) - Add HA\_VERIFY\_SSL toggle to disable TLS verification ([#​1104](homeassistant-ai/ha-mcp#1104)) - Per-top-level-key config\_hash for ha\_manage\_energy\_prefs ([#​1049](homeassistant-ai/ha-mcp#1049)) ([#​1098](homeassistant-ai/ha-mcp#1098)) - **site**: Add gemini-cli setup notes + compose hardening to wizard ([#​1027](homeassistant-ai/ha-mcp#1027)) ([#​1087](homeassistant-ai/ha-mcp#1087)) - Add convenience modes to ha\_manage\_energy\_prefs ([#​1050](homeassistant-ai/ha-mcp#1050)) ([#​1073](homeassistant-ai/ha-mcp#1073)) - Surface integration log levels in ha\_get\_logs/integration/addon ([#​956](homeassistant-ai/ha-mcp#956)) ([#​1003](homeassistant-ai/ha-mcp#1003)) - Expose allowlist\_external\_dirs in ha\_get\_overview full system\_info ([#​1053](homeassistant-ai/ha-mcp#1053)) - **dashboards**: Unify identifier handling in ha\_config\_\*\_dashboard tools ([#​981](homeassistant-ai/ha-mcp#981)) ([#​1075](homeassistant-ai/ha-mcp#1075)) - Include addon container logs in bug reports ([#​934](homeassistant-ai/ha-mcp#934)) - Add WebSocket response-shaping controls to ha\_manage\_addon ([#​1009](homeassistant-ai/ha-mcp#1009)) - Web-based settings UI for per-tool enable/disable/pin ([#​960](homeassistant-ai/ha-mcp#960)) - **site**: Add OpenCode support to setup wizard ([#​1080](homeassistant-ai/ha-mcp#1080)) ##### Changed - Clarify standard-mode HTTP deployment guidance ([#​1185](homeassistant-ai/ha-mcp#1185)) - Add Cloudflared add-on hostname alternative for tunnel service ([#​1183](homeassistant-ai/ha-mcp#1183)) - Align tool naming convention between AGENTS.md and styleguide ([#​943](homeassistant-ai/ha-mcp#943)) ([#​1174](homeassistant-ai/ha-mcp#1174)) - **addon**: Note tool-list ([#​985](homeassistant-ai/ha-mcp#985 divergence; fix [#​1139](https://github.com/homeassistant-ai/ha-mcp/issues/1139)/[#​1162](https://github.com/homeassistant-ai/ha-mcp/issues/1162) test conflict ([#​1172](homeassistant-ai/ha-mcp#1172)) - Add brew install option for mcp-proxy on macOS ([#​1171](homeassistant-ai/ha-mcp#1171)) - Update contributors list \[contributors-updated] ([`aba01a1`](homeassistant-ai/ha-mcp@aba01a1)) - Warn against enable\_tool\_search on Claude Sonnet/Opus ([#​1088](homeassistant-ai/ha-mcp#1088)) ([#​1140](homeassistant-ai/ha-mcp#1140)) - Address [#​1094](homeassistant-ai/ha-mcp#1094) review nits on OpenCode mirror comments ([#​1105](homeassistant-ai/ha-mcp#1105)) ##### Fixed - **integrations**: Surface ConfigEntry.options via OptionsFlow probe ([#​1245](homeassistant-ai/ha-mcp#1245)) - **backup**: Discover local agent at call time instead of hardcoding hassio.local ([#​1244](homeassistant-ai/ha-mcp#1244)) - Triage all 10 ha\_search\_entities behaviors from [#​1170](homeassistant-ai/ha-mcp#1170) ([#​1195](homeassistant-ai/ha-mcp#1195)) - Replace cron with systemd for demo server (prevents process leak) ([#​1110](homeassistant-ai/ha-mcp#1110)) - Improve ha\_manage\_addon discoverability (BM25 keywords + slug examples) ([#​1200](homeassistant-ai/ha-mcp#1200)) - Route Supervisor 401s to structured tool errors + add E2E coverage ([#​1129](homeassistant-ai/ha-mcp#1129)) ([#​1192](homeassistant-ai/ha-mcp#1192)) - Harden \_validate\_category\_id gate to cover dict-promoted category ([#​1190](homeassistant-ai/ha-mcp#1190)) - Broaden template anti-pattern detection + skill discoverability ([#​1011](homeassistant-ai/ha-mcp#1011)) ([#​1181](homeassistant-ai/ha-mcp#1181)) - Return newest automation traces, add offset+order pagination ([#​1177](homeassistant-ai/ha-mcp#1177)) ([#​1178](homeassistant-ai/ha-mcp#1178)) - **security**: Write YAML backups outside www/ (GHSA-g39v-cvjh-8fpf) ([#​1180](homeassistant-ai/ha-mcp#1180)) - **search**: Apply domain\_filter when area\_filter is set ([#​1162](homeassistant-ai/ha-mcp#1162)) ([#​1165](homeassistant-ai/ha-mcp#1165)) - **resources**: Reject HA-config YAML in dashboard resource content ([#​1160](homeassistant-ai/ha-mcp#1160)) - Close 19 bugs in ha\_config\_set\_helper (issue [#​1150](homeassistant-ai/ha-mcp#1150)) ([#​1151](homeassistant-ai/ha-mcp#1151)) - Route addon log fetches directly to supervisor on addon installs ([#​1126](homeassistant-ai/ha-mcp#1126)) - Survive read-only filesystems at startup ([#​1138](homeassistant-ai/ha-mcp#1138)) - **helpers**: Clarify name-required-on-create for ha\_config\_set\_helper ([#​1143](homeassistant-ai/ha-mcp#1143)) - Resolve disabled entities via entity\_registry in helper deletion ([#​1119](homeassistant-ai/ha-mcp#1119)) - Allow unary operators in python\_transform sandbox ([#​1118](homeassistant-ai/ha-mcp#1118)) - **site**: Add github-copilot-agents wizard branch + delete unreferenced data/clients.ts ([#​1108](homeassistant-ai/ha-mcp#1108)) - **addons**: Route addon API calls through HA Core ingress proxy ([#​1069](homeassistant-ai/ha-mcp#1069)) - **webhook-proxy**: Surface webhook registration failures instead of silently loading ([#​1101](homeassistant-ai/ha-mcp#1101)) - **site**: Resolve client display-order collisions and anchor OpenCode shape ([#​1094](homeassistant-ai/ha-mcp#1094)) ##### Performance Improvements - Dedupe lovelace/dashboards/list in ha\_config\_set\_dashboard ([#​1085](homeassistant-ai/ha-mcp#1085)) ([#​1191](homeassistant-ai/ha-mcp#1191)) ##### Refactoring - Drop obsolete ha\_mcp\_tools defensive ruamel.yaml imports ([post-#​1268](https://github.com/post-/ha-mcp/issues/1268)) ([#​1269](homeassistant-ai/ha-mcp#1269)) - Extract shared Supervisor httpx client helper ([#​1130](homeassistant-ai/ha-mcp#1130)) ([#​1203](homeassistant-ai/ha-mcp#1203)) - Surface client identity, AI model, config toggles, and prompt context in ha\_report\_issue ([#​1189](homeassistant-ai/ha-mcp#1189)) - Harden Context injection with safe-emit + branch coverage ([#​1173](homeassistant-ai/ha-mcp#1173)) - Consolidate area/floor set+remove tools (revisit of [#​813](homeassistant-ai/ha-mcp#813)) ([#​1139](homeassistant-ai/ha-mcp#1139)) - Pass verify\_ssl to remaining direct-Supervisor httpx callers ([#​1128](homeassistant-ai/ha-mcp#1128)) - Validate only new entries on convenience-mode writes ([#​1086](homeassistant-ai/ha-mcp#1086)) ([#​1100](homeassistant-ai/ha-mcp#1100)) *** <details> <summary>Internal Changes</summary> ##### Fixed - **ci**: Align pr.yml E2E with --dist loadscope ([#​1206](homeassistant-ai/ha-mcp#1206)) ([#​1247](homeassistant-ai/ha-mcp#1247)) - **ci**: Switch Renovate to a GitHub App token to allow workflow-file pushes ([#​1229](homeassistant-ai/ha-mcp#1229)) - **ci**: Break gemini-triage retrigger loop and bump turn budget ([#​1131](homeassistant-ai/ha-mcp#1131)) - **ci**: Harden gemini-triage so failures stop spamming user issues ([#​1122](homeassistant-ai/ha-mcp#1122)) - **ci**: Unbreak hotfix-release semantic-release run ([#​1091](homeassistant-ai/ha-mcp#1091)) ##### Chores - **addon**: Publish dev addon version 7.4.1.dev299 \[skip ci] ([`397aa6d`](homeassistant-ai/ha-mcp@397aa6d)) - **addon**: Publish dev addon version 7.4.1.dev298 \[skip ci] ([`942b7e0`](homeassistant-ai/ha-mcp@942b7e0)) - Sync tool docs after merge \[skip ci] ([`6823c47`](homeassistant-ai/ha-mcp@6823c47)) - **addon**: Publish dev addon version 7.4.1.dev297 \[skip ci] ([`6eac062`](homeassistant-ai/ha-mcp@6eac062)) - **addon**: Publish dev addon version 7.4.1.dev296 \[skip ci] ([`b2afe93`](homeassistant-ai/ha-mcp@b2afe93)) - **addon**: Publish dev addon version 7.4.1.dev295 \[skip ci] ([`4f4c4f3`](homeassistant-ai/ha-mcp@4f4c4f3)) - **deps**: Update ghcr.io/home-assistant/home-assistant docker tag to v2026.5.1 ([#​1236](homeassistant-ai/ha-mcp#1236)) - **addon**: Publish dev addon version 7.4.1.dev294 \[skip ci] ([`fd24991`](homeassistant-ai/ha-mcp@fd24991)) - **deps**: Update ghcr.io/astral-sh/uv docker tag to v0.11.13 ([#​1233](homeassistant-ai/ha-mcp#1233)) - **addon**: Publish dev addon version 7.4.1.dev293 \[skip ci] ([`fcc6496`](homeassistant-ai/ha-mcp@fcc6496)) - **addon**: Publish dev addon version 7.4.1.dev292 \[skip ci] ([`2961650`](homeassistant-ai/ha-mcp@2961650)) - **addon**: Publish dev addon version 7.4.1.dev291 \[skip ci] ([`5703112`](homeassistant-ai/ha-mcp@5703112)) - **addon**: Publish dev addon version 7.4.1.dev290 \[skip ci] ([`19b2f65`](homeassistant-ai/ha-mcp@19b2f65)) - **addon**: Publish dev addon version 7.4.1.dev289 \[skip ci] ([`e5a1365`](homeassistant-ai/ha-mcp@e5a1365)) - Sync tool docs after merge \[skip ci] ([`d2ff93b`](homeassistant-ai/ha-mcp@d2ff93b)) - **addon**: Publish dev addon version 7.4.1.dev288 \[skip ci] ([`0f62400`](homeassistant-ai/ha-mcp@0f62400)) - Sync tool docs after merge \[skip ci] ([`c7e2066`](homeassistant-ai/ha-mcp@c7e2066)) - **addon**: Publish dev addon version 7.4.1.dev287 \[skip ci] ([`c1133d4`](homeassistant-ai/ha-mcp@c1133d4)) - **addon**: Publish dev addon version 7.4.1.dev286 \[skip ci] ([`1ae790e`](homeassistant-ai/ha-mcp@1ae790e)) - **addon**: Publish dev addon version 7.4.1.dev285 \[skip ci] ([`2387d0c`](homeassistant-ai/ha-mcp@2387d0c)) - **addon**: Publish dev addon version 7.4.1.dev284 \[skip ci] ([`dd3a4a5`](homeassistant-ai/ha-mcp@dd3a4a5)) - **addon**: Publish dev addon version 7.4.1.dev283 \[skip ci] ([`78af8eb`](homeassistant-ai/ha-mcp@78af8eb)) - Sync tool docs after merge \[skip ci] ([`093fd74`](homeassistant-ai/ha-mcp@093fd74)) - **addon**: Publish dev addon version 7.4.1.dev282 \[skip ci] ([`2141e15`](homeassistant-ai/ha-mcp@2141e15)) - Sync tool docs after merge \[skip ci] ([`7810c95`](homeassistant-ai/ha-mcp@7810c95)) - **addon**: Publish dev addon version 7.4.1.dev281 \[skip ci] ([`7d79ec2`](homeassistant-ai/ha-mcp@7d79ec2)) - Sync tool docs after merge \[skip ci] ([`a73dc81`](homeassistant-ai/ha-mcp@a73dc81)) - **addon**: Publish dev addon version 7.4.1.dev280 \[skip ci] ([`c858ce3`](homeassistant-ai/ha-mcp@c858ce3)) - Sync tool docs after merge \[skip ci] ([`a587be0`](homeassistant-ai/ha-mcp@a587be0)) - **addon**: Publish dev addon version 7.4.1.dev279 \[skip ci] ([`b78ddb2`](homeassistant-ai/ha-mcp@b78ddb2)) - Sync tool docs after merge \[skip ci] ([`1210725`](homeassistant-ai/ha-mcp@1210725)) - **addon**: Publish dev addon version 7.4.1.dev278 \[skip ci] ([`a282c17`](homeassistant-ai/ha-mcp@a282c17)) - **addon**: Publish dev addon version 7.4.1.dev277 \[skip ci] ([`1081768`](homeassistant-ai/ha-mcp@1081768)) - Sync tool docs after merge \[skip ci] ([`e03f5d2`](homeassistant-ai/ha-mcp@e03f5d2)) - **addon**: Publish dev addon version 7.4.1.dev276 \[skip ci] ([`c4ef680`](homeassistant-ai/ha-mcp@c4ef680)) - **addon**: Publish dev addon version 7.4.1.dev275 \[skip ci] ([`780422d`](homeassistant-ai/ha-mcp@780422d)) - Sync tool docs after merge \[skip ci] ([`8a2bd1a`](homeassistant-ai/ha-mcp@8a2bd1a)) - **addon**: Publish dev addon version 7.4.1.dev274 \[skip ci] ([`f0f09de`](homeassistant-ai/ha-mcp@f0f09de)) - **addon**: Publish dev addon version 7.4.1.dev273 \[skip ci] ([`cb49f68`](homeassistant-ai/ha-mcp@cb49f68)) - **addon**: Publish dev addon version 7.4.1.dev272 \[skip ci] ([`5097186`](homeassistant-ai/ha-mcp@5097186)) - **addon**: Publish dev addon version 7.4.1.dev271 \[skip ci] ([`4714342`](homeassistant-ai/ha-mcp@4714342)) - **addon**: Publish dev addon version 7.4.1.dev270 \[skip ci] ([`217982a`](homeassistant-ai/ha-mcp@217982a)) - **addon**: Publish dev addon version 7.4.1.dev269 \[skip ci] ([`a65dd5f`](homeassistant-ai/ha-mcp@a65dd5f)) - Sync tool docs after merge \[skip ci] ([`0e6b54f`](homeassistant-ai/ha-mcp@0e6b54f)) - **addon**: Publish dev addon version 7.4.1.dev268 \[skip ci] ([`60ba1f2`](homeassistant-ai/ha-mcp@60ba1f2)) - **addon**: Publish dev addon version 7.4.1.dev267 \[skip ci] ([`13412aa`](homeassistant-ai/ha-mcp@13412aa)) - Sync tool docs after merge \[skip ci] ([`2702a0f`](homeassistant-ai/ha-mcp@2702a0f)) - **addon**: Publish dev addon version 7.4.1.dev266 \[skip ci] ([`77abe0b`](homeassistant-ai/ha-mcp@77abe0b)) - **addon**: Publish dev addon version 7.4.1.dev265 \[skip ci] ([`08b69db`](homeassistant-ai/ha-mcp@08b69db)) - Sync tool docs after merge \[skip ci] ([`c1f24b5`](homeassistant-ai/ha-mcp@c1f24b5)) - **addon**: Publish dev addon version 7.4.1.dev264 \[skip ci] ([`f2583f6`](homeassistant-ai/ha-mcp@f2583f6)) - Sync tool docs after merge \[skip ci] ([`c2ed2d3`](homeassistant-ai/ha-mcp@c2ed2d3)) - **addon**: Publish dev addon version 7.4.1.dev263 \[skip ci] ([`9d43e54`](homeassistant-ai/ha-mcp@9d43e54)) - **addon**: Publish dev addon version 7.4.1.dev262 \[skip ci] ([`a7355c8`](homeassistant-ai/ha-mcp@a7355c8)) - Sync tool docs after merge \[skip ci] ([`085bd8a`](homeassistant-ai/ha-mcp@085bd8a)) - Convert agents to skills ([#​1084](homeassistant-ai/ha-mcp#1084)) - **addon**: Publish dev addon version 7.4.1.dev261 \[skip ci] ([`0d1af36`](homeassistant-ai/ha-mcp@0d1af36)) - **addon**: Publish dev addon version 7.4.1.dev260 \[skip ci] ([`29397dc`](homeassistant-ai/ha-mcp@29397dc)) - **addon**: Publish dev addon version 7.4.1.dev259 \[skip ci] ([`4bbc74b`](homeassistant-ai/ha-mcp@4bbc74b)) - Sync tool docs after merge \[skip ci] ([`0f6d41e`](homeassistant-ai/ha-mcp@0f6d41e)) - **addon**: Publish dev addon version 7.4.1.dev258 \[skip ci] ([`6751d08`](homeassistant-ai/ha-mcp@6751d08)) - **addon**: Publish dev addon version 7.4.1.dev257 \[skip ci] ([`2213c89`](homeassistant-ai/ha-mcp@2213c89)) - **addon**: Publish dev addon version 7.4.1.dev256 \[skip ci] ([`18a366e`](homeassistant-ai/ha-mcp@18a366e)) - **addon**: Publish dev addon version 7.4.1.dev255 \[skip ci] ([`0e9b18d`](homeassistant-ai/ha-mcp@0e9b18d)) - **addon**: Publish dev addon version 7.4.1.dev254 \[skip ci] ([`39fc65b`](homeassistant-ai/ha-mcp@39fc65b)) - Sync tool docs after merge \[skip ci] ([`9fa0aea`](homeassistant-ai/ha-mcp@9fa0aea)) - **addon**: Publish dev addon version 7.4.1.dev253 \[skip ci] ([`0dcc59e`](homeassistant-ai/ha-mcp@0dcc59e)) - Sync tool docs after merge \[skip ci] ([`ec7413f`](homeassistant-ai/ha-mcp@ec7413f)) - **addon**: Publish dev addon version 7.4.1.dev252 \[skip ci] ([`345640c`](homeassistant-ai/ha-mcp@345640c)) - **addon**: Publish dev addon version 7.4.1.dev251 \[skip ci] ([`bab9d49`](homeassistant-ai/ha-mcp@bab9d49)) - Sync tool docs after merge \[skip ci] ([`726f0a5`](homeassistant-ai/ha-mcp@726f0a5)) - **addon**: Publish dev addon version 7.4.1.dev250 \[skip ci] ([`ded04ea`](homeassistant-ai/ha-mcp@ded04ea)) - **addon**: Publish dev addon version 7.4.1.dev249 \[skip ci] ([`37d5628`](homeassistant-ai/ha-mcp@37d5628)) - **addon**: Publish dev addon version 7.4.1.dev248 \[skip ci] ([`530786a`](homeassistant-ai/ha-mcp@530786a)) - Sync tool docs after merge \[skip ci] ([`36719c3`](homeassistant-ai/ha-mcp@36719c3)) - **addon**: Publish dev addon version 7.4.1.dev247 \[skip ci] ([`4dc47b5`](homeassistant-ai/ha-mcp@4dc47b5)) - **addon**: Publish dev addon version 7.4.1.dev246 \[skip ci] ([`6ffbd6a`](homeassistant-ai/ha-mcp@6ffbd6a)) - Sync tool docs after merge \[skip ci] ([`add66e3`](homeassistant-ai/ha-mcp@add66e3)) - **addon**: Publish dev addon version 7.4.1.dev245 \[skip ci] ([`d0114af`](homeassistant-ai/ha-mcp@d0114af)) - Sync tool docs after merge \[skip ci] ([`0ca41af`](homeassistant-ai/ha-mcp@0ca41af)) - **addon**: Publish dev addon version 7.4.1.dev244 \[skip ci] ([`d052dd0`](homeassistant-ai/ha-mcp@d052dd0)) - **addon**: Publish dev addon version 7.4.0.dev243 \[skip ci] ([`827bc65`](homeassistant-ai/ha-mcp@827bc65)) - Bump package version to 7.4.1 to match released addon ([`4f65497`](homeassistant-ai/ha-mcp@4f65497)) - **addon**: Publish dev addon version 7.4.0.dev242 \[skip ci] ([`8ba80ae`](homeassistant-ai/ha-mcp@8ba80ae)) - **addon**: Publish hotfix version 7.4.1 ([`bda75e6`](homeassistant-ai/ha-mcp@bda75e6)) - **addon**: Publish dev addon version 7.4.0.dev241 \[skip ci] ([`2126428`](homeassistant-ai/ha-mcp@2126428)) ##### Continuous Integration - **deps**: Bump renovatebot/github-action in the github-actions group ([#​1218](homeassistant-ai/ha-mcp#1218)) - **deps**: Bump renovatebot/github-action in the github-actions group ([#​1111](homeassistant-ai/ha-mcp#1111)) ##### Refactoring - Extract \_fetch\_dashboards\_list helper ([#​1193](homeassistant-ai/ha-mcp#1193)) ([#​1207](homeassistant-ai/ha-mcp#1207)) ##### Testing - **e2e**: Module-scope bulk\_automations + bulk\_scripts fixtures (refs [#​366](homeassistant-ai/ha-mcp#366)) ([#​1275](homeassistant-ai/ha-mcp#1275)) - **e2e**: Lower INPUT\_BOOLEAN\_WAIT from 30s to 10s (refs [#​366](homeassistant-ai/ha-mcp#366)) ([#​1273](homeassistant-ai/ha-mcp#1273)) - **e2e**: Generalize readiness-gate diagnostics helper (closes [#​1267](homeassistant-ai/ha-mcp#1267)) ([#​1271](homeassistant-ai/ha-mcp#1271)) - **e2e**: Narrow except clauses in e2e polling helpers (closes [#​1266](homeassistant-ai/ha-mcp#1266)) ([#​1270](homeassistant-ai/ha-mcp#1270)) - **e2e**: Drop ha\_mcp\_tools retry-path + pre-install manifest requirements ([#​1268](homeassistant-ai/ha-mcp#1268)) - **e2e**: Instrument and retry ha\_mcp\_tools readiness wait ([#​1262](homeassistant-ai/ha-mcp#1262)) - Use time.monotonic() in UAT runner and test\_env\_manager ([#​1254](homeassistant-ai/ha-mcp#1254)) - **e2e**: Detect partial/corrupt hacs\_frontend dir in fast-path guard ([#​1253](homeassistant-ai/ha-mcp#1253)) - **e2e**: Remove unused wait/assert helpers ([post-#​1249](https://github.com/post-/ha-mcp/issues/1249) audit) ([#​1256](homeassistant-ai/ha-mcp#1256)) - **e2e**: Clear stale .hacs\_frontend.lock from prior crashed runs ([#​1252](homeassistant-ai/ha-mcp#1252)) - **e2e**: Use time.monotonic() in workflow polling loops ([#​1258](homeassistant-ai/ha-mcp#1258)) - **e2e**: Use time.monotonic() for duration polling ([#​1234](homeassistant-ai/ha-mcp#1234)) ([#​1249](homeassistant-ai/ha-mcp#1249)) - **e2e**: Close ARM ha\_mcp\_tools readiness race under loadscope ([#​1208](homeassistant-ai/ha-mcp#1208)) - **hacs**: Tighten is\_hacs\_unavailable to not match legitimate "Repository not found" ([#​1246](homeassistant-ai/ha-mcp#1246)) - **seed**: Unblock 3 silent-skip pagination/state tests via baked recorder DB ([#​1240](homeassistant-ai/ha-mcp#1240)) - **seed**: Register a writable local\_calendar to unblock event-creation test ([#​1243](homeassistant-ai/ha-mcp#1243)) - **addon**: Fix base64 padding-bit flake in token tamper tests ([#​1238](homeassistant-ai/ha-mcp#1238)) ([#​1241](homeassistant-ai/ha-mcp#1241)) - **seed**: Add a writable scene for test\_call\_service\_scene\_turn\_on ([#​1231](homeassistant-ai/ha-mcp#1231)) - **seed**: Assign demo device to living\_room area for filter test ([#​1230](homeassistant-ai/ha-mcp#1230)) - **e2e**: Drop nonexistent sun service from session readiness wait ([#​1227](homeassistant-ai/ha-mcp#1227)) - **e2e**: Self-contain dashboard register/remove to fix ARM xdist race ([#​1196](homeassistant-ai/ha-mcp#1196)) ([#​1201](homeassistant-ai/ha-mcp#1201)) - Fix EN dash in docstring causing RUF002 lint failure ([`eac5916`](homeassistant-ai/ha-mcp@eac5916)) - Address Gemini review feedback on host detection and port allocation ([`960305e`](homeassistant-ai/ha-mcp@960305e)) - Fix three categories of E2E test flakiness ([`39417ff`](homeassistant-ai/ha-mcp@39417ff)) - **e2e**: Pin config\_hash stability for dashboards ([#​1132](homeassistant-ai/ha-mcp#1132)) </details> </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjAuNyIsInVwZGF0ZWRJblZlciI6IjQzLjE2MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19--> Co-authored-by: todd <tpunderson@greyrock.io> Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/26
What does this PR do?
Triage and fix every one of the 10 behaviors in ha_search_entities documented in #1170. Each finding is addressed with a targeted change plus regression tests; the alias work also closes #1166.
The 10 findings
domain_filteris case-sensitive (silent zero onLight).strip().lower()at both the tool and service-layer boundaries (and BEFORE the at-least-one-set validation, so a whitespace-only filter fails validation instead of falling through); the response echoes the canonical valuebedlighttieslight.bed_lightagainst five unrelated*_lightsat score 76area_onlyresults missscore+match_type;essential_attributesonly on fuzzy_searchscore=100,match_type="area_match"so the shape matches the other four search-type branches; dropessential_attributesfrom fuzzy_search for full shape parityarea_filtered_queryechoesarea_filterper-result (asymmetric)area_filterstill present)xyz_irrelevant_garbage→cover.garage_doorat score 92;litsurfaces 186*_lite*entities at 85ligth(5 chars) unaffected_partial_results_searchreturned a useless score-0 entity dumppartial: truenoisearea_onlyreturns the first matched area only (and from aset, so non-deterministic)area_names: list[str]joins legacyarea_name. Exact id/name/alias matches short-circuit fuzzy aggregation so a literal area_id no longer leaks sibling areas' entitiesentity_registry.aliasesinto the BM25 corpus via a single batchedget_entriescall; consultarea_registry.aliasesin the area resolver. Closes #1166.hidden_by) outrank visible peers under identical match conditionsinclude_hidden: bool = Trueparameter is the explicit opt-out for filtering them entirelyTestSearchEntitiesSeededAreasIssue1170test class against thetests/initial_test_stateseedImplementation notes
_strip_separatorshelper exposes elided forms (bed_light→bedlight) as BM25 tokens. Single-token queries that omit separators now hit a unique high-IDF token directly instead of falling through to typo_fallback's tie-cluster._typo_fallback, multi-token queries must have ≥50% of their distinct tokens fuzzy-match some doc token. Garbage queries with one accidental hit (garbage~garage) are rejected. Single-token queries shorter than 4 chars also skip the fallback ("lit"no longer surfaces every*_lite*entity at score 85); legitimate typos like"ligth"are unaffected.config/entity_registry/listdeliberately omitsaliases— we now batch-fetch viaconfig/entity_registry/get_entriesafter the hidden filter, so the extra round-trip stays bounded by the post-filter survivor set. Same approach inget_entities_by_areafor area-resolved entities. Alias-driven matches surface asmatch_type="alias_match".next(iter(area_result["areas"].values()))against asetupstream — non-deterministic AND lossy. Now sorts byarea_idand iterates all.area_names: list[str]is the new field;area_name(singular) is retained as the first match for one minor version of soft-compat. Exact id/name/alias matches now short-circuit fuzzy aggregation:area_filter="bedroom_kids"no longer fuzzy-matches its parent"bedroom"(partial_ratio=100) and aggregates sibling areas' entities.apply_hidden_penalty(score, hidden_by)helper infuzzy_search.pysubtracts a 20-point penalty wheneverhidden_byis non-None. Wired through every branch that emits ascorefield — BM25, typo_fallback, exact_match, area_only, area_filtered_query, domain_listing — so the contract is uniform: a hidden exact-id match (raw 100 → 80) still ranks above a fuzzy threshold-floor visible match (60-70), but loses to any visible entity scoring 80+. The threshold gate runs on the raw score so a borderline hidden match doesn't silently disappear.include_hidden=Falseretains the explicit opt-out for callers that need visible-only results.except ToolError: raiseguard, so structured failures fromsmart_entity_searchpropagate instead of being retried via the substring fallback. Alias-enrichment warnings carry a structuredalias_enrichment_failedprefix and the survivor count for log bucketing.asyncio.gather(return_exceptions=True)capturesasyncio.CancelledErrorlike any other exception. Three call sites (smart_entity_search,_exact_match_search, the empty-query+domain branch) now explicitly re-raise it so the canceller doesn't wait forever.fuzzy_searchmain path,_exact_match_search,area_only,domain_listing) now tie-break onentity_idfor stable pagination — the hidden-penalty banding (visible@100, hidden@80) made the within-tier reordering particularly likely without the secondary key.domain_filter/area_filterstrip+lowercase now happens BEFORE the at-least-one-set validation. Previously a whitespace-only filter (" ") passed validation truthy then collapsed to""and fell through to a silent zero-result fuzzy search.ValueErrorfromcoerce_bool_param/coerce_int_paramis now caught separately and surfaced viacreate_validation_errorwith the helper's own message and no boilerplate operational suggestions (previouslylimit=0returned"Check Home Assistant connection"next to"limit must be at least 1, got 0").strip_internal_fields(recursive, mutating, cycle-guarded) andpublic_fields(shallow, non-mutating) intools/util_helpers.pycentralise the leading-underscore convention so_hidden_by/_aliasesenrichments don't leak through public tool returns.server.py:get_entities_by_areabridge now strips its delegate's output.area_filterresolves to populated areas butdomain_filterwipes out every entity, the response now carries"No <domain> entities found in area: <area>"— previously the empty-area branch had a message but the populated-but-domain-filtered-empty branch was silent.Public API impact
include_hiddenparameter (defaultTrue);area_names: list[str]onarea_only;match_type="alias_match"result value.area_onlyresults now includescore+match_type(fix: resolve GitHub Action semantic-release configuration issues #3).area_filtered_queryno longer emitsarea_filterper-result (top-level still present) (Potential fix for code scanning alert no. 1: Workflow does not contain permissions #4).search_type="partial_listing"no longer returned (refactor: split ha_manage_* into ha_config_{get,set,remove}_* tools #6) — failures now surface asToolErrorinstead of a zero-scored entity dump.fuzzy_searchno longer emitsessential_attributes(matches the other four branches' shape).domain_filteris.strip().lower()normalized before matching, and echoed in normalized form (ci(deps): bump python-semantic-release/python-semantic-release from 9.10.0 to 10.4.1 in the actions group #1).hidden_by != null) now sort below comparable visible matches due to a 20-point score penalty. Default-Trueinclude_hiddenkeeps them in results — setinclude_hidden=Falsefor the visible-only behavior. No callers have a smaller result set than before unless they explicitly opt out.Type of change
Testing
uv run pytest)uv run ruff check)Test additions:
TestFuzzySearcherIssue1170intests/src/unit/test_bm25_search.py: concat-token elision, multi-token coverage gate, alias-match labeling, single-token typo recall, alias-vs-name precedence, short single-token min-length gate ("lit"returns empty;"ligth"still recalls).TestSmartEntitySearchPropagation— locks down the finding-6 contract thatget_statesfailures surface asToolError.TestHiddenScorePenalty— locks down the option-(c) ranking contract: helper math, visible-vs-hidden ordering on shared queries, zero-clamp boundary, threshold-on-raw-score edge case (raw-100 hidden surfaces at score 80 even when threshold=100), typo_fallback penalty, CancelledError propagation, sort tie-breaker ordering.TestStripInternalFieldsandTestPublicFieldsintests/src/unit/test_util_helpers_internal_strip.py— full coverage of the leak-guard helpers (recursion, cycles, non-string keys, mutation semantics, shallow-copy contract).TestExactMatchSearchCancelledPropagationintest_search_fallback.py—_exact_match_searchre-raises CancelledError from the registry task.tests/src/unit/test_server_bridge_strip.py—server.py:get_entities_by_areabridge actually invokesstrip_internal_fields.tests/src/e2e/tools/test_search_entities.py: one per runtime-observable finding, plus area-alias resolution, concat-token elision through the full pipeline, area_filtered_query + hidden penalty, domain_listing + hidden penalty, determinism ofarea_namesordering, exact area_id short-circuits fuzzy aggregation, shape consistency across all 5 search_types, validation error has no generic suggestions, area+domain zero-overlap message, parametrised whitespace test fordomain_filter(5 variants), whitespace-only filter rejected at validation, hidden-with-penalty /include_hidden=Falsepair across substring + fuzzy + area_only branches.TestSearchEntitiesSeededAreasIssue1170class with 3 test methods exercising area_filter branches at realistic seeded-area populations (feat: Docker deployment and Home Assistant add-on support #10).test_search_fallback.pyrewritten for option (c) — assert hidden entities are present with reduced score, and assertinclude_hidden=Falsefilters them out.seeded_bedroomfixture wrapped in try/finally so a fixture-body failure (orpytest.skip) doesn't leak the seed area assignments.Test updates:
valid_search_typesin the response-structure regression test no longer accepts the deletedpartial_listing.test_search_fallback.pyandtest_search_pagination.pyupdated to drop references to the deleted_partial_results_search.Future improvements
None tracked — every triage item from #1170, plus all findings surfaced during live stress testing on a real HA instance, is addressed in this PR.
Checklist
🤖 Generated with Claude Code