[codex] fix email cursor replay on first sync#298
Conversation
|
@claude Do a thorough review of this PR in terms of: KISS, YAGNI, DRY, Fail Fast, Secure by Default and performance/speed |
|
Claude finished @furukama's task in 2m 27s —— View job PR Review: KISS · YAGNI · DRY · Fail Fast · Secure by Default · Performance
The fix is correct and the logic is sound. A few targeted observations below, ordered by principle. KISSDead let lastProcessedUid = compatibleStoredCursor
? compatibleStoredCursor.lastProcessedUid
: 0; // ← this value is never readWhen const compatibleStoredCursor = ...;
const maxKnownUid = resolveMailboxUidNext(activeClient) - 1;
if (!compatibleStoredCursor) {
persistedCursorState.set(folder, { uidValidity, lastProcessedUid: maxKnownUid });
await savePersistedFolderCursorState(config.address, persistedCursorState);
return;
}
let lastProcessedUid = compatibleStoredCursor.lastProcessedUid; // always defined hereDRYDuplicated Both tests define a nearly identical function makeImapFlowMock(opts: { uidNext: () => number; search: Mock; fetch: Mock; messageFlagsAdd: Mock }) { ... }Cursor path construction duplicated in tests ( The second test manually reconstructs the cursor file path using the same base64url + strip-padding logic that lives in Consider exporting Pre-existing: Fail FastNo guard on If if (!compatibleStoredCursor) {
if (maxKnownUid > 0) { // only seed if the mailbox reported a real head
persistedCursorState.set(folder, { uidValidity, lastProcessedUid: maxKnownUid });
await savePersistedFolderCursorState(config.address, persistedCursorState);
}
return;
}YAGNINothing to flag. The change is minimal and precisely scoped to the bug. No speculative abstractions introduced. Secure by DefaultNothing to flag in the new code. Minor pre-existing note: PerformancePre-existing — await activeClient.search({ all: true }, { uid: true })This returns every UID in the folder and filters client-side. For a large inbox (thousands of messages), this transfers the full UID list on every poll. An IMAP await activeClient.search({ uid: `${lastProcessedUid + 1}:*` }, { uid: true })This is pre-existing and out of scope here, but worth a follow-up ticket. Pre-existing — per-message file writes: Summary
The core fix is correct and well-tested. The two actionable items for this PR are the dead-code simplification and the |
There was a problem hiding this comment.
Pull request overview
Fixes the email IMAP cursor bootstrap behavior so that when there is no compatible persisted cursor (missing file, wrong uidValidity, etc.), the connection manager seeds lastProcessedUid from the current mailbox head rather than replaying the whole inbox. This restores the pre-persisted-cursor startup semantics and adds regression coverage for both fresh-start and restart flows.
Changes:
- Seed and persist the cursor to
uidNext - 1when no compatible stored cursor exists, and skip processing on that first poll. - Add/adjust unit tests to validate (1) first start does not process historical UIDs, and (2) restart with a saved cursor processes only messages that arrived while offline.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/channels/email/connection.ts |
Updates cursor bootstrap logic to persist a baseline cursor at mailbox head when no compatible cursor exists, preventing historical replay. |
tests/email.connection.test.ts |
Reworks existing test to assert no replay on missing cursor, and adds a new test ensuring saved-cursor resumes correctly. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
What changed
Fix the email IMAP cursor bootstrap logic so a missing or incompatible persisted cursor seeds from the current mailbox head instead of replaying the entire inbox.
Add regression coverage for both startup modes:
Why
After the April 11, 2026 email cursor change, the first gateway start without an existing cursor file initialized
lastProcessedUidto0and searched all mailbox UIDs. That caused historical inbox mail to be treated as new and replied to again.Impact
Email users no longer get duplicate replies after upgrading into the persisted-cursor implementation.
Root cause
The persisted cursor path did not preserve the old channel behavior of treating the current mailbox head as the startup baseline when no cursor existed yet.
Validation
./node_modules/.bin/vitest run --configLoader runner --config vitest.unit.config.ts tests/email.connection.test.ts