Wait for HA to list a new event before reporting it created

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 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-20 21:08:06 +02:00
co-authored by Claude Opus 5
parent 10686cf383
commit 5977ad0928
+33 -5
View File
@@ -3,7 +3,7 @@ import logging
import secrets import secrets
import ssl import ssl
import time import time
from datetime import datetime, timezone from datetime import datetime, timedelta, timezone
from typing import Optional from typing import Optional
from urllib.parse import urlencode from urllib.parse import urlencode
@@ -248,6 +248,35 @@ def _ha_create_event(url: str, token: str, entity_id: str, data: dict) -> dict:
return {} 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): 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 """Update an event. Tries calendar/event/update first; if the integration
doesn't support update (e.g. Google Calendar via HA), falls back to doesn't support update (e.g. Google Calendar via HA), falls back to
@@ -697,10 +726,9 @@ def create_event(
account = cal.account account = cal.account
token = _get_valid_token(account, db) token = _get_valid_token(account, db)
try: try:
_ha_create_event( payload = data.model_dump(exclude_none=True)
account.url, token, cal.entity_id, _ha_create_event(account.url, token, cal.entity_id, payload)
data.model_dump(exclude_none=True), _ha_wait_until_listed(account.url, token, cal.entity_id, payload)
)
return {"ok": True} return {"ok": True}
except Exception as exc: except Exception as exc:
raise HTTPException(500, f"HA event create failed: {exc}") raise HTTPException(500, f"HA event create failed: {exc}")