Skip to content

Commit 30766aa

Browse files
fix(entities): refetch entity state after exposure so the response reflects new should_expose (#1697)
* fix(entities): refetch entity state after exposure so the response reflects new should_expose When a phase before exposure already populated entity_entry (a registry field update, an options update, or a device rename), the post-exposure refetch in _apply_expose_to was gated on `if not entity_entry` and was therefore skipped, so ha_set_entity returned the pre-exposure snapshot -- its options[...].should_expose did not reflect the exposure just applied. The exposure was always applied correctly; only the returned entity was stale. Refetch now also fires when exposure was applied (exposure_result is not None). Pure expose_to-only calls are unchanged. Regression from #1692. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(entities): don't fail ha_set_entity when the cosmetic post-exposure refetch fails PR review of the refetch surfaced three issues, all fixed here: - A failed post-exposure refetch could turn a fully-committed combined call (registry/options/device-rename + expose) into a reported ENTITY_NOT_FOUND. When a prior phase already produced a snapshot, warn and return it instead. - The failure swallowed the real WebSocket error and hard-coded "not found"; now surfaces _extract_ws_error + has_registry_updates/options_succeeded. - Removed the dead `or not entity_entry` clause. Tests added for the fallback and the options-only / new_device_name combos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(entities): make post-exposure refetch unconditional The `if exposure_result is not None` guard is always True at that point (empty expose_to returns early; any exposure failure raises above), so the refetch is now unconditional, per Gemini review feedback on #1697. Adds test_expose_hide_only_still_refetches to pin that a hide-only expose (should_expose=False, a truthy {assistant: False} result) still refetches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1193579 commit 30766aa

2 files changed

Lines changed: 319 additions & 25 deletions

File tree

src/ha_mcp/tools/tools_entities.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -745,30 +745,45 @@ async def _apply_expose_to(
745745

746746
exposure_result: dict[str, bool] | None = succeeded if succeeded else None
747747

748-
# If no prior phase populated entity_entry, fetch current entity state
749-
if not entity_entry:
750-
get_msg: dict[str, Any] = {
751-
"type": "config/entity_registry/get",
752-
"entity_id": entity_id,
753-
}
754-
get_result = await self._client.send_websocket_message(get_msg)
755-
if get_result.get("success"):
756-
entity_entry = get_result.get("result") or {}
757-
else:
758-
raise_tool_error(
759-
create_error_response(
760-
ErrorCode.ENTITY_NOT_FOUND,
761-
f"Entity '{entity_id}' not found in registry after applying exposure changes",
762-
context={
763-
"entity_id": entity_id,
764-
"exposure_succeeded": exposure_result,
765-
},
766-
suggestions=[
767-
"Verify the entity_id exists using ha_search()",
768-
"The entity's exposure settings were likely changed, but its current state could not be confirmed.",
769-
],
770-
)
748+
# Exposure mutates the registry entry's options, so refetch to return the
749+
# post-exposure state. This is unconditional: the method returns early on
750+
# an empty expose_to (line 695) and any exposure failure already raised
751+
# above, so reaching here always means at least one exposure was applied.
752+
get_msg: dict[str, Any] = {
753+
"type": "config/entity_registry/get",
754+
"entity_id": entity_id,
755+
}
756+
get_result = await self._client.send_websocket_message(get_msg)
757+
if get_result.get("success"):
758+
entity_entry = get_result.get("result") or {}
759+
elif entity_entry:
760+
# The exposure already committed; only the cosmetic post-exposure
761+
# refresh failed. Keep the pre-exposure snapshot and warn rather
762+
# than reporting the whole (successful) operation as a failure.
763+
logger.warning(
764+
f"Exposure applied to {entity_id} but its registry state could "
765+
f"not be refreshed: {_extract_ws_error(get_result)}"
766+
)
767+
else:
768+
# No prior-phase snapshot to fall back on -- surface the read
769+
# failure with the actual WebSocket error.
770+
raise_tool_error(
771+
create_error_response(
772+
ErrorCode.ENTITY_NOT_FOUND,
773+
f"Entity '{entity_id}' could not be read after applying "
774+
f"exposure changes: {_extract_ws_error(get_result)}",
775+
context={
776+
"entity_id": entity_id,
777+
"exposure_succeeded": exposure_result,
778+
"has_registry_updates": has_registry_updates,
779+
"options_succeeded": options_succeeded,
780+
},
781+
suggestions=[
782+
"Verify the entity_id exists using ha_search()",
783+
"The entity's exposure settings were likely changed, but its current state could not be confirmed.",
784+
],
771785
)
786+
)
772787

773788
return exposure_result, entity_entry
774789

0 commit comments

Comments
 (0)