From ba66c6475025e8123e88732f5cec71e5b56b0b14 Mon Sep 17 00:00:00 2001 From: andienguyen-ecoligo Date: Sat, 21 Feb 2026 12:36:04 -0500 Subject: [PATCH 1/2] fix(email): evict oldest half of dedup set instead of clearing entirely When _processed_uids exceeds 100k entries, the entire set was cleared with .clear(), allowing all previously seen emails to be re-processed. Now evicts the oldest 50% of entries, keeping recent UIDs to prevent duplicate processing while still bounding memory usage. Fixes #890 --- nanobot/channels/email.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nanobot/channels/email.py b/nanobot/channels/email.py index 1b6f46b..c90a14d 100644 --- a/nanobot/channels/email.py +++ b/nanobot/channels/email.py @@ -304,7 +304,9 @@ class EmailChannel(BaseChannel): self._processed_uids.add(uid) # mark_seen is the primary dedup; this set is a safety net if len(self._processed_uids) > self._MAX_PROCESSED_UIDS: - self._processed_uids.clear() + # Evict oldest half instead of clearing entirely + to_keep = list(self._processed_uids)[len(self._processed_uids) // 2:] + self._processed_uids = set(to_keep) if mark_seen: client.store(imap_id, "+FLAGS", "\\Seen") From 4e8c8cc2274f1c5f505574e8fa7d5a5df790aff6 Mon Sep 17 00:00:00 2001 From: Re-bin Date: Sun, 22 Feb 2026 15:48:49 +0000 Subject: [PATCH 2/2] fix(email): fix misleading comment and simplify uid eviction --- nanobot/channels/email.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nanobot/channels/email.py b/nanobot/channels/email.py index c90a14d..5dc05fb 100644 --- a/nanobot/channels/email.py +++ b/nanobot/channels/email.py @@ -304,9 +304,8 @@ class EmailChannel(BaseChannel): self._processed_uids.add(uid) # mark_seen is the primary dedup; this set is a safety net if len(self._processed_uids) > self._MAX_PROCESSED_UIDS: - # Evict oldest half instead of clearing entirely - to_keep = list(self._processed_uids)[len(self._processed_uids) // 2:] - self._processed_uids = set(to_keep) + # Evict a random half to cap memory; mark_seen is the primary dedup + self._processed_uids = set(list(self._processed_uids)[len(self._processed_uids) // 2:]) if mark_seen: client.store(imap_id, "+FLAGS", "\\Seen")