Skip to content

feat(mail): budgeted syncMail, per-message retry, cause_code in runs, 15 min for a sync - #2144

Merged
leemour merged 3 commits into
mainfrom
feat/mail-sync-budget
Oct 29, 2026
Merged

leemour merged 3 commits into
mainfrom
feat/mail-sync-budget

Conversation

@leemour

@leemour leemour commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Task

Make a mail sync on Yandex 360 finish reliably (mail-cli on @wirecat/cli-core/mail): spend a request budget, keep mid-folder progress, cope with a server that fails single messages and closes the connection, and say why a run failed.

Why

Real runs on a 253,024-message INBOX: the server answers single messages with * NO [UNAVAILABLE] UID FETCH ... failure due to backend error and then closes the connection, so every later command failed. The sync also listed the whole window (a header FETCH per message) before saving anything, so a 16,000-message window never saved a resume point under a deadline.

What

  • syncMail(..., { budget }) (WC-LIFE-16): rounds of source.listBatch messages, one request of the budget per search, list and fetchRaw call, resume point saved after every round, state: "partial", stopped, remaining, requests in the answer, max defaults to no limit. MailSource gains the optional listBatch and fetchBatch. Without a budget behaviour is unchanged, except that a failed chunk saves the resume point before it throws.
  • A message the source does not return from list, or answers with an Error from fetchRaw, is asked for once more at the end of the run; if it still fails it is in unreadable and not in the resume point, so the next run asks again.
  • A pass that read the window in parts proves no deletion (deletionsSkipped says so), except when one round lists all of it. Known gap: a window larger than one round never proves a deletion under a budget.
  • runs show: error.cause_code from details.cause_code (plain codes only).
  • DEFAULT_SYNC_MS (15 min): a cooperative command that declares partial gets it at a terminal; other cooperative commands keep 5 min, MCP keeps 45 s. The calendar sync is such a command, so it goes from 5 to 15 minutes. Owner's choice, see the cli-base draft.

Testing

pnpm lint, pnpm typecheck, pnpm docs:check, pnpm test (622 files, 8,149 tests). New tests: budgeted slices and request count, stop and resume, one retry in the same run, a message that stays unreadable is not stored as done, the 15 minute class, cause_code in the run record.

🤖 Generated with Claude Code

leemour and others added 3 commits October 29, 2026 13:23
… 15 min for a sync

syncMail takes a RunBudget (WC-LIFE-16). It reads the window in rounds of
source.listBatch messages, one request of the budget per call to the source,
saves the resume point after every round so a 16,000-message folder finishes
over several runs, and answers state partial with remaining and requests when
the budget stops it. A message the source did not return or answered with an
Error is asked for once more at the end of the run, and if it still fails it is
counted in unreadable and left out of the resume point. A failed chunk now
saves the resume point before it throws.

A failed run's record keeps the transport's own code as error.cause_code.

A cooperative command that declares partial (a resumable sync) gets 15 minutes
at a terminal (DEFAULT_SYNC_MS); other cooperative commands keep 5 minutes and
MCP keeps 45 seconds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@leemour
leemour merged commit 5ac26b4 into main Oct 29, 2026
32 checks passed
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