From ffdfa585cccc2826e66c09691d5eb2f5f10e89a5 Mon Sep 17 00:00:00 2001 From: sysops Date: Tue, 1 Sep 2026 15:01:23 +0200 Subject: [PATCH] fix(PROJ-86): defensive UID-Filterung gegen kaputten IMAP-Server (Sync-Loop) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit q.sysops.de (Konto patrick@sysops.de) beantwortet "UID SEARCH UID N:*" auf Mailboxen mit weniger Nachrichten als N fehlerhaft und liefert die letzte existierende UID statt eines leeren Ergebnisses zurück (verifiziert per Debug-Client: "UID SEARCH UID 22:*" auf 21-Nachrichten-Mailbox -> [21]). Ohne Filterung wurde dieselbe UID bei jedem 5-Minuten-Sync erneut als "neu" behandelt und re-indexiert - dabei driftete auch date_ts bei Mails ohne Date-Header ständig auf "jetzt". Server-Antwort wird jetzt client-seitig re-gefiltert, nur UIDs >= angefragtem Minimum zählen als neu. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UPFC6Jk2ke1Pq9XcuVGP1R --- internal/imap/scheduler.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/imap/scheduler.go b/internal/imap/scheduler.go index 86ecf65..062e6bd 100644 --- a/internal/imap/scheduler.go +++ b/internal/imap/scheduler.go @@ -398,7 +398,17 @@ func (s *Scheduler) syncFolder( if err != nil { return 0, 0, fmt.Errorf("imap scheduler: uid search incremental %q: %w", folder, err) } - uids = searchData.AllUIDs() + // Defensive re-filter: some IMAP servers mishandle open-ended "N:*" + // UID ranges and return UIDs below N anyway (observed against + // q.sysops.de — "UID SEARCH UID 22:*" on a 21-message mailbox + // returned [21] instead of an empty set). Trusting that blindly + // makes the sync loop rediscover the same top message as "new" + // forever, every cycle. Never trust the server's range filtering. + for _, uid := range searchData.AllUIDs() { + if uid >= minUID { + uids = append(uids, uid) + } + } } else { // First sync or post-UIDVALIDITY-reset: fetch everything. searchData, err := c.UIDSearch(&imapv2.SearchCriteria{}, nil).Wait()