Skip to content

fix: keep ctx.last_seen_uid in sync during IDLE mode#53

Open
Xitee1 wants to merge 1 commit intomainfrom
fix/idle-stale-last-seen-uid
Open

fix: keep ctx.last_seen_uid in sync during IDLE mode#53
Xitee1 wants to merge 1 commit intomainfrom
fix/idle-stale-last-seen-uid

Conversation

@Xitee1
Copy link
Copy Markdown
Owner

@Xitee1 Xitee1 commented Mar 28, 2026

Summary

  • Bug: In IDLE mode, fetch_new_emails persisted the last processed UID to the database via save_uid() but never updated the in-memory ctx.last_seen_uid. Since IDLE mode reuses the same FetchContext across notification cycles, every IDLE notification caused all previously-processed emails to be re-fetched from the IMAP server and re-checked against the dedup table. The overhead grew linearly with each new email during a session.
  • Fix: Added ctx.last_seen_uid = uid after each save_uid() call (2 lines). Polling mode was already unaffected since it creates a fresh context each cycle.
  • Tests: Added 3 new tests covering context updates after processing, after route-skip, and no-op when empty. Full suite: 225 passed.

Test plan

  • All 225 backend tests pass (222 existing + 3 new)
  • Manual verification: connect an IMAP account in IDLE mode, send multiple emails, confirm logs show only new UIDs being fetched (no re-fetching of old ones)

🤖 Generated with Claude Code

In IDLE mode, fetch_new_emails reused a stale FetchContext across
notification cycles. The UID was persisted to DB via save_uid but the
in-memory ctx.last_seen_uid was never updated, causing all previously
processed emails to be re-fetched from IMAP on every IDLE notification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall: The core 2-line fix is correct and well-targeted. A few minor items below.

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core fix is correct and well-targeted. A few items noted inline.


**Verification:** All 222 existing tests pass. Added 3 new tests in `tests/test_fetch_new_emails.py` covering: (1) ctx updates after processing emails, (2) ctx updates when emails are skipped by routing, (3) ctx unchanged when no new emails. Full suite: 225 passed.

**Risk:** None identified. The `FetchContext` is a non-frozen dataclass, mutation is safe. The `ctx` is only used within the `fetch_new_emails` → `idle_loop` call chain. Polling mode already creates fresh contexts and is unaffected.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file duplicates information already in the PR description and commit messages. It will go stale quickly and adds maintenance burden. Consider removing it — git history and the PR are the appropriate places for this context.

"""Tests for fetch_new_emails keeping ctx.last_seen_uid in sync."""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MagicMock and WorkerState (line 11) are imported but never used. Remove them to keep the test file clean.

imap = AsyncMock()
uid_str = " ".join(str(u) for u in uids).encode()
imap.uid_search = AsyncMock(return_value=("OK", [uid_str] if uids else [b""]))
imap.uid = AsyncMock(return_value=("OK", [bytearray(email_bytes)]))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion (test gap): This mock returns the same email for every UID and doesn't validate the search criteria string. Consider adding an assertion that uid_search is called with the expected UID {last_seen_uid + 1}:* criteria — that's the core invariant this fix protects. Without it, these tests would still pass even if the ctx.last_seen_uid update were removed (the mock doesn't change behavior based on UID range).

Also: for test_ctx_last_seen_uid_updated_after_processing, consider testing with a second call to fetch_new_emails on the same ctx to verify that the updated last_seen_uid actually affects the next IDLE cycle's search range. That would be a true integration-style test of the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant