From 5977ad0928123cdd4698bf686b782d6bf92e4255 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Tue, 20 Oct 2026 21:08:06 +0200 Subject: [PATCH] Wait for HA to list a new event before reporting it created MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Home Assistant confirms calendar/event/create before its entity's event list reflects it. Both clients already force a refetch after saving and the server caches nothing, so the refetch simply lost the race: the event was created but invisible until the next manual reload. Users read that as a failure, retry, and end up with a duplicate. Fixed once here rather than having web, iOS and Android each guess a delay. Bounded at two seconds and silent on failure — the event exists either way, so a slow Home Assistant must not turn a successful create into an error. Co-Authored-By: Claude Opus 5 --- backend/routers/homeassistant_router.py | 38 +++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/backend/routers/homeassistant_router.py b/backend/routers/homeassistant_router.py index 4eb5b6a..d281409 100644 --- a/backend/routers/homeassistant_router.py +++ b/backend/routers/homeassistant_router.py @@ -3,7 +3,7 @@ import logging import secrets import ssl import time -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from typing import Optional from urllib.parse import urlencode @@ -248,6 +248,35 @@ def _ha_create_event(url: str, token: str, entity_id: str, data: dict) -> dict: return {} +def _ha_wait_until_listed(url: str, token: str, entity_id: str, data: dict) -> None: + """Block until HA actually lists the event we just created. + + HA confirms the create before its entity's event list reflects it, so a + client that refetches straight away misses the new event, the user assumes + it failed, retries — and ends up with a duplicate. Waiting here fixes every + client at once instead of each one guessing a delay. + + Gives up quietly: the event exists either way, it may just need one more + refresh. Never turn a successful create into an error. + """ + try: + start = datetime.fromisoformat(_ha_format_dt(data.get("start", ""))) + end = datetime.fromisoformat(_ha_format_dt(data.get("end", ""))) + except Exception: + return + title = data.get("title", "") + # Widen the window so an all-day event or a timezone edge cannot hide it. + window_start, window_end = start - timedelta(days=1), end + timedelta(days=1) + for _ in range(8): + time.sleep(0.25) + try: + raw = _ha_get_events(url, token, entity_id, window_start, window_end) + except Exception: + return + if any(ev.get("summary") == title for ev in raw): + return + + def _ha_update_event(url: str, token: str, entity_id: str, uid: str, data: dict): """Update an event. Tries calendar/event/update first; if the integration doesn't support update (e.g. Google Calendar via HA), falls back to @@ -697,10 +726,9 @@ def create_event( account = cal.account token = _get_valid_token(account, db) try: - _ha_create_event( - account.url, token, cal.entity_id, - data.model_dump(exclude_none=True), - ) + payload = data.model_dump(exclude_none=True) + _ha_create_event(account.url, token, cal.entity_id, payload) + _ha_wait_until_listed(account.url, token, cal.entity_id, payload) return {"ok": True} except Exception as exc: raise HTTPException(500, f"HA event create failed: {exc}")