fix(email): use UID commands instead of sequence numbers in IMAP fetches (#5149)

conn.search() / conn.fetch() operate on volatile positional sequence
numbers that shift whenever messages are deleted or expunged. Three call
sites in the sig-learner (_pull_headers, _fetch_bodies) and morning-brief
email section were storing these as "uid" and reusing them in subsequent
fetches — causing wrong-message returns or NO responses if another client
modified the mailbox concurrently.

Replaced with conn.uid("SEARCH", ...) / conn.uid("FETCH", ...), which use
persistent RFC 3501 UIDs. _scan_one (urgency action) already did this
correctly; these were the remaining callers.

The reproduction window is narrow (requires concurrent deletion between
search and fetch), so the fix is verified by regression tests rather than
manual end-to-end: _SpyImap raises AssertionError if conn.search() or
conn.fetch() are called instead of conn.uid().
This commit is contained in:
Peter Karlsson
2026-07-11 07:06:40 -06:00
committed by GitHub
parent 2531ba401c
commit 801c3a2ff1
3 changed files with 121 additions and 15 deletions
+7 -9
View File
@@ -109,10 +109,9 @@ async def test_learn_sender_signatures_resolves_llm_for_task_owner(monkeypatch):
def select(self, *_args, **_kwargs):
return "OK", []
def search(self, *_args, **_kwargs):
return "OK", [b"1 2 3"]
def fetch(self, _uid, _query):
def uid(self, command, *_args):
if command == "SEARCH":
return "OK", [b"1 2 3"]
return "OK", [(None, b"From: Writer <writer@example.com>\r\n\r\n")]
def logout(self):
@@ -171,11 +170,10 @@ async def test_learn_sender_signatures_writes_owner_scoped_cache(monkeypatch, tm
def select(self, *_args, **_kwargs):
return "OK", []
def search(self, *_args, **_kwargs):
return "OK", [b"1 2 3"]
def fetch(self, uid, query):
if "HEADER.FIELDS" in query:
def uid(self, command, uid=None, query=None):
if command == "SEARCH":
return "OK", [b"1 2 3"]
if query and "HEADER.FIELDS" in query:
return "OK", [(None, b"From: Writer <writer@example.com>\r\n\r\n")]
return "OK", [
(