Conversation
PR #1 discovers addressbooks via DAV/CardDAV namespaces, then only keeps members whose getcontenttype contains "vcard". iCloud omits that property and names contacts *.vcf, so listing returned zero URLs. Treat a vcard MIME type or a .vcf href as a contact, and skip collection hrefs that end with a slash.
Build on push to main and fix/carddav-icloud-vcf, push to ghcr.io/hubeight/bdaysync. Do not tag latest.
The previous commit replaced the repo workflow that was already running on this branch via workflow_dispatch.
Trivy failed because it used HubEight (must be lowercase) and fix/carddav-icloud-vcf (slash is invalid in a Docker tag). Use the same sanitized tag docker/metadata-action publishes.
HubEight
force-pushed
the
fix/carddav-icloud-vcf
branch
from
September 14, 2026 16:51
10a1a5c to
7db3ebd
Compare
iCloud lookups occasionally fail with ENETUNREACH (IPv6 without a route). Retry the GET up to three times with a short backoff so one blip does not drop a contact for the whole run.
After a complete CardDAV fetch, remove calendar events whose UID
matches birthday-{slug}-{YYYYMMDD} if that slug is not in the current
set of contacts that still have a birthday. Skip delete when any vCard
GET failed or the calendar listing is empty.
The Trivy image-ref fix is for this fork's GHCR job (HubEight vs hubeight, slashes in branch tags). It does not belong in a PR to anatosun/bdaysync.
fetch_complete compared fetched vCards with listed ones, so a failed addressbook listing (HTTP error, exception, broken XML) was never counted and still yielded complete=True. Unparseable birthdays were counted as fetched, and an empty contact list with a complete fetch deleted every birthday event. - Clear fetch_complete on any listing, download or parse failure; _parse_vcard now raises on unreadable data and returns None only when there is no BDAY. Listing PROPFIND gets a timeout. - Restore main_sync returning False when no contacts were found, so orphan delete never runs with an empty set. - Match orphans on name slug plus month/day, so a changed birthday replaces the old event instead of leaving a duplicate. - Share the UID slug between event creation, lookup and orphan delete. - The retry docstring blamed IPv6: urllib3 tries every resolved address and IPv4 sorts first, so ENETUNREACH is only the last error. Add test_sync.py covering each case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Orphan delete removes calendar data, so it is opt-in. Wired like the other BIRTHDAY_* settings: config, .env.template, docker-compose and README, and logged with the event configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is based on #1 (the XML/namespace discovery for servers like SOGo). I kept that and added a few things that showed up when I pointed it at iCloud.
iCloud doesn't send a vcard content-type on the address book members. The files are just named .vcf. With only the content-type check, listing returned zero contacts even though the book was found. I treat .vcf hrefs as contacts as well.
Network to iCloud is a bit flaky from my setup (occasional "network unreachable" on a single GET). Those GETs are retried a couple of times instead of dropping the contact for that run.
bdaysync also never removed calendar events. If I delete the birthday on a contact, the event stayed in the calendar. There is now an opt-in cleanup,
BIRTHDAY_DELETE_ORPHANS(defaultfalse). It drops birthday-* events whose contact no longer has that name and birthday, so a changed date replaces the old event as well. It only runs after a complete fetch: a failed address book listing, a failed vCard GET, an unparseable birthday or an empty contact list all skip it, so a blip doesn't wipe the calendar.bdaysync/test_sync.pycovers these cases (cd bdaysync && python -m unittest test_sync).I ran this against a real iCloud account: contacts come through, events show up in the birthday calendar, and removing the date on a test contact removed the event.