Skip to content

fix(gmail): make void inbox mirror Gmail's INBOX label - #64

Open
MaximeGaudin wants to merge 1 commit into
mainfrom
fix/gmail-inbox-reconcile
Open

fix(gmail): make void inbox mirror Gmail's INBOX label#64
MaximeGaudin wants to merge 1 commit into
mainfrom
fix/gmail-inbox-reconcile

Conversation

@MaximeGaudin

Copy link
Copy Markdown
Owner

Fixes #63

Summary

void inbox --connector gmail and void gmail search 'in:inbox' returned different sets for the same mailbox. Three independent bugs caused the drift:

  1. 7-day window corrupted reconcileinitial_sync/refresh_inbox listed INBOX with newer_than:7d, then reconcile_inbox archived every message not in that partial set. INBOX mail older than 7d was never fetched and actively archived locally (the "in Gmail INBOX but not void inbox" orphans).
  2. Expired historyId failed silently — Gmail returns 404 once history is purged; the error body deserialized into an empty HistoryListResponse, so incremental sync no-oped forever and archive-state drift persisted until daemon restart (the "in void inbox but not Gmail INBOX" stragglers).
  3. API errors parsed as empty listslist_messages/get_message ignored HTTP status, so a transient 4xx/5xx during refresh yielded an empty INBOX set and reconcile archived every message locally.

Changes

  • refresh_inbox lists the complete INBOX (no date filter, 500/page, 20-page cap); reconcile is skipped with a warning if the cap truncates, so a partial listing can never archive live INBOX mail. Bodies are fetched only for unknown messages. initial_sync now reuses this path.
  • incremental_sync falls back to a full INBOX refresh and resumes from a fresh historyId on expiry.
  • API client now enforces error_for_status(); history 404 maps to a new GmailError::HistoryExpired.
  • Sync methods split into *_with_api variants for mock-server testing.
  • README documents that void inbox mirrors Gmail's INBOX label and is thread-level (latest unarchived message per thread) vs the message-level gmail search listing (issue point 3).

Testing

  • New regression tests: full-INBOX reconcile without date filter (asserts q param absent, both archive directions, new-message fetch) and history-404 fallback (reconcile + fresh historyId).
  • Full workspace: 613 tests pass, clippy and fmt clean.

Existing installations self-heal on the next sync: reconcile un-archives everything still labeled INBOX in Gmail.

void inbox and `gmail search 'in:inbox'` disagreed on the same mailbox
(#63). Three causes:

- refresh/initial sync listed INBOX with newer_than:7d and reconciled
  against that partial set, archiving older INBOX mail locally and never
  fetching it. Now lists the complete INBOX (500/page, 20-page cap) and
  skips reconcile with a warning if the cap truncates.
- an expired historyId (404) deserialized into an empty history
  response, so incremental sync failed silently forever. Now surfaces
  GmailError::HistoryExpired and falls back to a full INBOX refresh with
  a fresh historyId.
- list_messages/get_message ignored HTTP status, so API errors parsed
  as empty lists — a transient failure during refresh would archive
  every message locally. Now error_for_status() is enforced.

Also documents the thread-level inbox mapping in the README.

Fixes #63
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.

gmail: void inbox and gmail search in:inbox disagree on the same mailbox

1 participant