@@ -66,6 +66,7 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to
6666from gaia .agents .base .memory import MemoryMixin
6767from gaia .agents .base .tools import _TOOL_REGISTRY
6868from gaia .connectors .errors import ConnectorsError
69+ from gaia .connectors .formatting import format_connector_error
6970from gaia .connectors .providers .base import ConnectorRequirement
7071from gaia .database .mixin import DatabaseMixin
7172from gaia .llm .lemonade_client import DEFAULT_MODEL_NAME
@@ -489,6 +490,13 @@ def _triage_all_backends(self, *, max_messages: int) -> dict:
489490 because local inference is slow (~9-31 s/email) and a doubled budget
490491 would blow the user's expected wait. Every returned item gains a
491492 ``mailbox`` tag and its id is remembered for downstream action routing.
493+
494+ When one backend raises ``ConnectorsError`` (e.g. an agent grant was
495+ revoked while the connection remains live), the error is recorded as a
496+ per-mailbox notice in ``mailbox_errors`` and the loop continues with the
497+ remaining backends. Non-``ConnectorsError`` exceptions still propagate —
498+ a genuine bug must fail loudly. The available set stays connection-derived;
499+ grant enforcement happens at the token layer.
492500 """
493501 from gaia_agent_email .tools import read_tools
494502 from gaia_agent_email .tools .read_tools import (
@@ -509,17 +517,24 @@ def _triage_all_backends(self, *, max_messages: int) -> dict:
509517 backends = self ._backends
510518 per_backend = max (1 , max_messages // len (backends ))
511519 merged : list [dict ] = []
520+ mailbox_errors : list [dict ] = []
512521 for provider , backend in backends .items ():
513522 if len (merged ) >= max_messages :
514523 break
515- out = triage_inbox_impl (
516- backend ,
517- max_messages = per_backend ,
518- session_preferences = prefs ,
519- force_llm = force_llm ,
520- classifier = classifier ,
521- debug = debug_flag ,
522- )
524+ try :
525+ out = triage_inbox_impl (
526+ backend ,
527+ max_messages = per_backend ,
528+ session_preferences = prefs ,
529+ force_llm = force_llm ,
530+ classifier = classifier ,
531+ debug = debug_flag ,
532+ )
533+ except ConnectorsError as exc :
534+ msg = format_connector_error (exc )
535+ mailbox_errors .append ({"mailbox" : provider , "error" : msg })
536+ logger .warning ("email triage: skipping %s mailbox — %s" , provider , msg )
537+ continue
523538 for item in out ["results" ]:
524539 item ["mailbox" ] = provider
525540 self ._remember_message_mailbox (item .get ("id" ), provider )
@@ -541,7 +556,17 @@ def _triage_all_backends(self, *, max_messages: int) -> dict:
541556 self ._apply_behavioral_promotions ()
542557 # Re-group the merged, capped list so the bucketed view matches what the
543558 # caller actually sees.
544- return {"results" : merged , "grouped" : group_by_category (merged )}
559+ if mailbox_errors and len (mailbox_errors ) == len (self ._backends ):
560+ # Every connected mailbox failed — surface it loudly rather than
561+ # returning ok with zero results (which reads as "empty inbox").
562+ raise ConnectorsError (
563+ "All connected mailboxes failed during triage: "
564+ + "; " .join (f"{ e ['mailbox' ]} : { e ['error' ]} " for e in mailbox_errors )
565+ )
566+ result : dict = {"results" : merged , "grouped" : group_by_category (merged )}
567+ if mailbox_errors :
568+ result ["mailbox_errors" ] = mailbox_errors
569+ return result
545570
546571 def _apply_behavioral_promotions (self ) -> None :
547572 """Promote qualifying senders to priority based on observed reply behavior.
@@ -593,6 +618,10 @@ def _pre_scan_all_backends(self, *, max_messages: int) -> dict:
593618 (urgent / actionable / suggested_archives) gains a ``mailbox`` tag and
594619 its message_id is remembered for action routing. Per-section caps and
595620 the envelope shape are preserved by merging the per-backend envelopes.
621+
622+ When one backend raises ``ConnectorsError`` (e.g. a revoked agent grant),
623+ the error is recorded in ``mailbox_errors`` and the loop continues with
624+ the remaining backends. Non-``ConnectorsError`` exceptions still propagate.
596625 """
597626 from gaia_agent_email .tools .read_tools import (
598627 PRE_SCAN_ACTIONABLE_CAP ,
@@ -613,16 +642,25 @@ def _pre_scan_all_backends(self, *, max_messages: int) -> dict:
613642 informational_count = 0
614643 scanned = 0
615644 merged_prefs_applied : dict = {}
645+ mailbox_errors : list [dict ] = []
616646 for provider , backend in backends .items ():
617647 if scanned >= max_messages :
618648 break
619- out = pre_scan_inbox_impl (
620- backend ,
621- max_messages = per_backend ,
622- session_preferences = prefs ,
623- force_llm = force_llm ,
624- debug = debug_flag ,
625- )
649+ try :
650+ out = pre_scan_inbox_impl (
651+ backend ,
652+ max_messages = per_backend ,
653+ session_preferences = prefs ,
654+ force_llm = force_llm ,
655+ debug = debug_flag ,
656+ )
657+ except ConnectorsError as exc :
658+ msg = format_connector_error (exc )
659+ mailbox_errors .append ({"mailbox" : provider , "error" : msg })
660+ logger .warning (
661+ "email pre-scan: skipping %s mailbox — %s" , provider , msg
662+ )
663+ continue
626664 # Count messages actually returned, not the cap — an under-filled
627665 # backend would otherwise trip the budget guard and skip a later one.
628666 backend_totals = out .get ("totals" , {})
@@ -649,7 +687,7 @@ def _pre_scan_all_backends(self, *, max_messages: int) -> dict:
649687 self ._remember_message_mailbox (item .get ("thread_id" ), provider )
650688 suggested_archives .append (item )
651689 informational_count += int (out .get ("informational_count" , 0 ))
652- return {
690+ result = {
653691 "kind" : "email_pre_scan" ,
654692 "urgent" : urgent [: max (0 , PRE_SCAN_URGENT_CAP )],
655693 "actionable" : actionable [: max (0 , PRE_SCAN_ACTIONABLE_CAP )],
@@ -664,6 +702,16 @@ def _pre_scan_all_backends(self, *, max_messages: int) -> dict:
664702 "suggested_archives" : len (suggested_archives ),
665703 },
666704 }
705+ if mailbox_errors and len (mailbox_errors ) == len (self ._backends ):
706+ # Every connected mailbox failed — surface it loudly rather than
707+ # returning ok with zero results (which reads as "empty inbox").
708+ raise ConnectorsError (
709+ "All connected mailboxes failed during pre-scan: "
710+ + "; " .join (f"{ e ['mailbox' ]} : { e ['error' ]} " for e in mailbox_errors )
711+ )
712+ if mailbox_errors :
713+ result ["mailbox_errors" ] = mailbox_errors
714+ return result
667715
668716 # -- Phase I3 batch-organize counter -----------------------------------
669717
0 commit comments