diff --git a/routes/contacts_routes.py b/routes/contacts_routes.py index 2b357216a..de0a5d041 100644 --- a/routes/contacts_routes.py +++ b/routes/contacts_routes.py @@ -689,15 +689,24 @@ def _delete_contact(uid: str) -> bool: url = _resolve_resource_url(uid) auth = (cfg["username"], cfg["password"]) if cfg["username"] else None r = httpx.delete(url, auth=auth, timeout=10) - if r.status_code in (200, 204): - _contact_cache["fetched_at"] = None - return True - if r.status_code == 404: - # Resource not found at the resolved URL. With href resolution - # this should be rare (genuinely already deleted). Invalidate - # the cache and report success so the UI doesn't keep a ghost. - logger.info(f"CardDAV DELETE 404 for {uid} — treating as already gone") + if r.status_code in (200, 204, 404): + # Invalidate cache so the next fetch sees the server truth. _contact_cache["fetched_at"] = None + # Verify: force a fresh fetch and check the UID is actually gone. + # A 404 on the guessed URL ({uid}.vcf) can mean the contact + # lives at a different resource URL — the DELETE missed it but + # we'd silently report success. This check catches that. + fresh = _fetch_contacts(force=True) + still_there = any(c.get("uid") == uid for c in fresh) + if still_there: + logger.warning( + f"CardDAV DELETE reported success for {uid} " + f"but UID still present after re-fetch — " + f"resource URL may differ from {url}" + ) + return False + if r.status_code == 404: + logger.info(f"CardDAV DELETE 404 for {uid} — already gone") return True logger.warning(f"CardDAV DELETE returned {r.status_code}: {r.text[:200]}") return False