47d5af90d317bffde6f0843beabd2d72c88fbfce
lrnassar
  Tue Mar 10 08:18:06 2026 -0700
Adding retry logic around the gmail API call, because it fails periodically (but then works next time it runs). Refs #36801

diff --git src/utils/qa/mlqAutomate.py src/utils/qa/mlqAutomate.py
index 748fee08c08..30d93b7f1bd 100755
--- src/utils/qa/mlqAutomate.py
+++ src/utils/qa/mlqAutomate.py
@@ -14,30 +14,31 @@
 import html as html_module
 import time
 from datetime import datetime, timedelta
 from difflib import SequenceMatcher
 import email
 from email import policy
 from email.utils import parseaddr
 from email.mime.text import MIMEText
 from functools import wraps
 import pytz
 import requests
 from google.oauth2.credentials import Credentials
 from google_auth_oauthlib.flow import InstalledAppFlow
 from google.auth.transport.requests import Request
 from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
 import anthropic
 
 # Configuration
 CONFIG = {
     'REDMINE_URL': 'https://redmine.gi.ucsc.edu',
     'REDMINE_API_KEY': '',
     'REDMINE_PROJECT': 'maillists',
     'CALENDAR_ID': 'ucsc.edu_anbl4254jlssgo3gc2l5c8un5c@group.calendar.google.com',
     'CLAUDE_API_KEY': '',
 
     # Mailing lists
     'MODERATED_LISTS': ['genome@soe.ucsc.edu', 'genome-mirror@soe.ucsc.edu'],
     'UNMODERATED_LISTS': ['genome-www@soe.ucsc.edu'],
 
     # Name mapping from calendar to Redmine
@@ -459,30 +460,31 @@
             else:
                 content = msg.get_payload()
 
         if content_type == 'text/plain':
             body = content
         elif content_type == 'text/html':
             html_body = content
 
     # If no text/plain, convert HTML to text
     if not body and html_body:
         body = html_to_text(html_body)
 
     return subject, sender, body
 
 
+@retry(max_attempts=3, delay=2, exceptions=(HttpError,))
 def get_pending_moderation_emails(group_name):
     """Get pending moderation notification emails for a group."""
     creds = get_google_credentials()
     service = build('gmail', 'v1', credentials=creds, cache_discovery=False)
 
     # Search for pending moderation emails for this group (exclude trash)
     query = f'subject:"{group_name} - soe.ucsc.edu admins: Message Pending" -in:trash'
     results = service.users().messages().list(userId='me', q=query, maxResults=50).execute()
 
     pending = []
     for msg_ref in results.get('messages', []):
         msg = service.users().messages().get(userId='me', id=msg_ref['id'], format='full').execute()
         headers = {h['name']: h['value'] for h in msg['payload']['headers']}
 
         # Extract the approval address from the From header