Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(email): one ungranted mailbox no longer aborts the whole multi-mailbox scan (#1739)#1753

Merged
itomek merged 6 commits into
mainfrom
tmi/issue-1739-connection-derived-clean-grant-error
Jun 18, 2026
Merged

fix(email): one ungranted mailbox no longer aborts the whole multi-mailbox scan (#1739)#1753
itomek merged 6 commits into
mainfrom
tmi/issue-1739-connection-derived-clean-grant-error

Conversation

@itomek

@itomek itomek commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Closes #1739

Why this matters

Before: if one connected mailbox lacked a per-agent grant, its eager auth error propagated out of the multi-mailbox scan loop and turned the entire triage/pre-scan into one error — discarding the granted mailbox's results too. After: the scan catches the typed connectors error per-provider, records a clean per-mailbox notice (mailbox_errors), and keeps going, so the granted mailbox still triages.

This also resolves #1707's open question with a documented decision: the available-mailbox/calendar set stays connection-derived on purpose — grants are enforced at the connection/token layer and the agent stays grant-unaware (no grant/scope logic added to the agent).

Test plan

  • Unit: python -m pytest tests/unit/agents/email/test_grant_revoked_clean_error.py -q — triage + pre-scan continue when one mailbox is ungranted; the connected-but-ungranted provider still appears in the available set; calendar yields a clean error; injected-fake/eval seam unaffected. Full email suite: 390 passed.
  • Real-world (2026-06-18, fully reversible — touches only the local grants ledger):
Real-world capture
revoke microsoft grant for installed:email
  available set            → ['google','microsoft']   (connection-derived, unchanged)
  pre_scan_inbox           → ok:True   (scan did NOT abort)
  mailbox_errors           → [{mailbox: microsoft, error: <grant message>}]
  google                   → not in mailbox_errors (kept working)
re-grant exact scopes
  pre_scan_inbox           → ok:True, mailbox_errors []
  grant restored byte-for-byte → True
RESULT: PASS

Note: the per-mailbox error text shown for a Microsoft grant gap is currently Google-centric — a separate, pre-existing defect in the connectors formatting layer, filed as #1751 (Milestone 40). It's out of scope here by design (this PR keeps the agent grant-unaware; message wording is owned by the connectors layer).

Tomasz Iniewicz and others added 4 commits June 18, 2026 14:19
When one connected mailbox has its agent grant revoked,
_triage_all_backends and _pre_scan_all_backends currently propagate
the AuthRequiredError out of the loop, aborting the whole multi-mailbox
scan. The two failing tests document the expected behavior: the granted
mailbox results come back, and the ungranted provider appears in
mailbox_errors with an actionable message instead of crashing.

Resolves: #1739
… scan (#1739)

Before: _triage_all_backends and _pre_scan_all_backends iterated all
backends with no per-backend try/except. One ungranted backend's
AuthRequiredError propagated out of the loop and turned the whole scan
into a single error envelope — the granted mailboxes' already-computed
results were discarded.

After: each backend call is wrapped in try/except ConnectorsError. On
a per-provider error the message is recorded in mailbox_errors and the
loop continues. Granted mailboxes keep working; the ungranted provider
appears in mailbox_errors with the actionable message from
format_connector_error. Non-ConnectorsError exceptions still propagate
— genuine bugs fail loudly.

Also documents the design decision in config.py: the available set is
intentionally connection-derived; grant enforcement is the connectors
layer's job (get_access_token_sync raises AuthRequiredError eagerly).
@itomek itomek marked this pull request as ready for review June 18, 2026 19:55
@itomek itomek requested a review from kovtcharov-amd as a code owner June 18, 2026 19:55
@github-actions github-actions Bot added the tests Test changes label Jun 18, 2026
@itomek itomek marked this pull request as draft June 18, 2026 19:55
@github-actions

Copy link
Copy Markdown
Contributor

Review — #1753 fix(email): one ungranted mailbox no longer aborts the whole multi-mailbox scan

Summary

Solid, well-scoped resilience fix: a per-provider try/except ConnectorsError around each backend's triage/pre-scan call lets a granted mailbox keep returning results when a sibling mailbox's grant is revoked, with the failure recorded in a new mailbox_errors list instead of propagating out of the loop. The change is correct, additive (no caller of the old return shape breaks since mailbox_errors only appears when non-empty), and matches GAIA's "fail loudly" rule — the error is surfaced cleanly rather than swallowed, and non-ConnectorsError exceptions still propagate. The single most important thing to know: this is a draft PR and the new test couldn't be executed in my environment (no pytest), so the green-test claim rests on your local 390 passed run — worth a maintainer re-run before un-drafting.

The design decision to keep the available set connection-derived (grant enforcement deferred to the token layer) is the right call and is now well-documented in both config.py docstrings and the closing of #1707's open question.

Issues

🟢 Minor — format_connector_error(exc) is computed twice per error (agent.py:534-542, again at agent.py:658-666)

Both the mailbox_errors entry and the logger.warning call format the same exception independently. Compute once:

except ConnectorsError as exc:
    msg = format_connector_error(exc)
    mailbox_errors.append({"mailbox": provider, "error": msg})
    logger.warning("email triage: skipping %s mailbox — %s", provider, msg)
    continue

Same pattern applies to the _pre_scan_all_backends block. Trivial, non-blocking.

🟢 Minor — local import is inconsistent with the module's own convention (agent.py:507, agent.py:632)

format_connector_error is imported locally inside each method, but the module already imports from gaia.connectors.errors import ConnectorsError at top level (agent.py:68), so the connectors import chain is already loaded at module-import time — the local import buys nothing here. (This differs from config.py, where local backend imports are deliberate to keep config import-time free of the connectors chain — that rationale doesn't apply to agent.py.) Promoting it to a top-level import would be more consistent, but it's harmless as-is.

🟢 Minor (note, not a change request) — mailbox_errors has no dedicated consumer

Nothing reads result["mailbox_errors"] other than the tests; it reaches the user only because the agent loop/LLM sees it in the tool's JSON envelope. That's a legitimate surfacing path for an agent tool, so no action needed — just flagging that there's no guarantee the model verbalizes a skipped mailbox to the user. If you want a hard guarantee, a follow-up could render it in the console/UI envelope. Out of scope for this PR.

Strengths

  • Catches the base ConnectorsError, not just AuthRequiredError — correctly resilient to the broader connector-layer failure family for one mailbox while still recording (never swallowing) the error. The continue keeps per-mailbox counters (scanned, merged) untouched, so the budget guard stays correct.
  • Genuinely good test coverage: the suite asserts the granted mailbox's results survive, the ungranted provider lands in mailbox_errors with an actionable message, the available set stays connection-derived, the all-granted path is unaffected (regression guard), and the calendar single-backend path returns a clean envelope via the existing per-tool catch. The TDD ordering (failing tests first in f813d592, fix in c50f819c) is clean.
  • Honest scoping: the Google-centric error wording for a Microsoft gap is correctly identified as a pre-existing connectors-layer defect (bug(connectors): email agent AGENT_NOT_GRANTED message hardcodes Google, misleads on Outlook/Microsoft grant gaps #1751) and left out of scope rather than patched in the agent — keeps the agent grant-unaware as intended.

Verdict

Approve with suggestions. No blocking issues — the two 🟢 nits (duplicate formatting call, local-vs-top-level import) are optional polish. Recommend a maintainer re-runs pytest tests/unit/agents/email/ before this leaves draft, since the test pass is self-reported and I couldn't execute it here.

@itomek

itomek commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Addressed both 🟢 nits in c27e055:

  • Hoisted format_connector_error to a top-level import in agent.py — the connectors import chain is already loaded via the top-level ConnectorsError import (agent.py:68), so the local imports bought nothing.
  • Compute format_connector_error(exc) once per except block (into msg), reused for both the mailbox_errors entry and the logger.warning — in both _triage_all_backends and _pre_scan_all_backends.

The "mailbox_errors has no dedicated consumer" note and the Google-centric wording for a Microsoft gap (tracked in #1751) are intentionally left out of scope here. Full email unit suite still green (390 passed).

@kovtcharov-amd kovtcharov-amd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving — the per-mailbox except ConnectorsError correctly isolates a revoked-grant failure to a single mailbox while letting genuine bugs (non-ConnectorsError) propagate, which matches the fail-loudly rule. AuthRequiredError subclasses ConnectorsError and the import is already on main, so the catch resolves cleanly. The connection-derived available-set decision is well documented, and the TDD tests cover triage, pre-scan, the calendar single-backend path, and the all-granted regression. Nicely scoped.

@itomek itomek marked this pull request as ready for review June 18, 2026 20:06
@github-actions

Copy link
Copy Markdown
Contributor

Review: fix(email) — one ungranted mailbox no longer aborts the whole scan (#1739)

Summary

Solid, well-scoped fix that does exactly what it says: a single revoked-grant mailbox no longer takes down the whole multi-mailbox triage/pre-scan. The change wraps each backend call in a typed except ConnectorsError, records a per-provider notice in mailbox_errors, and continues — so the granted mailbox still returns results. It reuses the established format_connector_error(exc) convention already used across calendar_tools.py/delete_tools.py, keeps the agent grant-unaware (the documented #1707 decision), and lets non-ConnectorsError exceptions propagate so genuine bugs still fail loudly. The single most important thing to know: the new mailbox_errors field is produced but not yet consumed anywhere in code — it rides in the tool-result JSON for the LLM to surface, so make sure the agent actually tells the user a mailbox was skipped.

Issues

🟢 mailbox_errors has no in-code consumer; surfacing relies entirely on the LLM (agent.py:560, agent.py:698)
The field is returned in the tool envelope but nothing in the agent or prompt explicitly guides surfacing it. In practice the LLM sees the JSON and can mention it, which is the normal tool-result pattern — but a quiet model could drop it and the user would silently get partial results. Worth confirming the email agent's system prompt nudges it to report skipped mailboxes, or adding a one-line note to do so. Not blocking.

🟢 All-mailboxes-ungranted returns ok: True with empty results (agent.py:559, agent.py:681)
When every backend errors, the scan returns success with an empty result set plus a populated mailbox_errors. It's not a silent fallback (the errors are present), but "total failure → ok:True, zero emails" leans on the same LLM-surfacing path as above to avoid reading as "your inbox is empty." Consider whether a wholly-failed scan should be distinguishable. Discussion, not a required change.

Strengths

  • Pattern reuse over reinventionexcept ConnectorsError: format_connector_error(exc) mirrors the existing per-tool handlers in calendar_tools.py:843, so the fix reads as idiomatic to the module rather than bolted on.
  • Tight exception scope — catching ConnectorsError (parent of AuthRequiredError) while letting everything else propagate respects the repo's fail-loudly rule; a real bug in triage_inbox_impl still surfaces.
  • Genuinely strong test coveragetest_grant_revoked_clean_error.py is TDD-shaped (the failing-against-main intent is documented), covers both triage and pre-scan loops, the connection-derived available-set invariant, the all-granted regression guard, and the calendar single-backend path. Hermetic fakes, no live connectors.
  • Honest scope discipline — the Google-centric error wording for a Microsoft gap is acknowledged and deferred to bug(connectors): email agent AGENT_NOT_GRANTED message hardcodes Google, misleads on Outlook/Microsoft grant gaps #1751 rather than patched ad hoc here, keeping the agent grant-unaware as designed.

Verdict

Approve. No blocking issues. The two 🟢 points are about how clearly the skipped-mailbox notice reaches the end user — worth a glance at the system prompt, but not a merge blocker. (Note: I could not run the test suite in this sandbox — pytest isn't installed here — but the file is well-formed and the PR reports 390 passing; the maintainer has already approved.)

kovtcharov-amd
kovtcharov-amd previously approved these changes Jun 18, 2026
…1739)

Per review of #1753: a scan where every connected mailbox errors returned
ok:True with empty results (reads as 'empty inbox'). Now raises a
ConnectorsError summarizing every mailbox failure, so the tool envelope is
ok:False. Partial failure (>=1 mailbox succeeds) still returns results +
mailbox_errors. Adds all-ungranted tests for triage and pre-scan.
@itomek

itomek commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — both 🟢 points addressed/considered:

  • All-mailboxes-ungranted → ok:True empty results (point 2): fixed in 557e843. When every connected mailbox errors, _triage_all_backends / _pre_scan_all_backends now raise a ConnectorsError summarizing each mailbox failure, so the tool envelope is ok:False (fail-loudly) instead of reading as "empty inbox". Partial failure (≥1 mailbox succeeds) still returns results + mailbox_errors. Added all-ungranted tests for both scan loops. Full email suite: 392 passed.
  • Surfacing partial skips via the prompt (point 1): intentionally not bundled here — the system prompt is an LLM-affecting surface that needs an email eval run to change responsibly, so I've kept this PR to the deterministic scan-resilience change. With the fix above, the total-failure case is now loud regardless of the model; the residual (one mailbox skipped, quiet model) rides the standard tool-result path and is a reasonable prompt follow-up.

@itomek itomek added this pull request to the merge queue Jun 18, 2026
Merged via the queue into main with commit 292f708 Jun 18, 2026
33 checks passed
@itomek itomek deleted the tmi/issue-1739-connection-derived-clean-grant-error branch June 18, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test changes

Projects

None yet

2 participants