#!/usr/bin/env python3 """ PR Stale Monitor - Check for stale PRs and send reminders Runs hourly to check PRs that have been open for more than 6 hours without being closed """ import os import sys import json import re import subprocess import requests from datetime import datetime # Load environment variables from bashrc try: with open(os.path.expanduser('~/.bashrc')) as f: for line in f: if 'FEISHU_CODE_EXPERT' in line and line.startswith('export'): match = re.search(r'export (\w+)=\"(.*?)\"', line) if match: key, value = match.groups() os.environ.setdefault(key, value) except Exception: # Ignore parse errors # Best-effort: bashrc loading, vars handled elsewhere pass # Configuration REPO = "tidbcloud/data-service-infra-ai-configurations" FEISHU_CHAT_ID = "oc_f0e4a980f9ec5e0bcf2111856c7e981f" FEISHU_APP_ID = os.environ.get("FEISHU_CODE_EXPERT_APP_ID", "") FEISHU_APP_SECRET = os.environ.get("FEISHU_CODE_EXPERT_APP_SECRET", "") STALE_HOURS = 6 def get_feishu_token(): """Get Feishu app access token""" url = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal" headers = {"Content-Type": "application/json"} data = {"app_id": FEISHU_APP_ID, "app_secret": FEISHU_APP_SECRET} resp = requests.post(url, headers=headers, json=data, timeout=30) result = resp.json() if result.get("code") == 0: return result.get("app_access_token") print(f"Failed to get Feishu token: {result}") return None def send_feishu_message(token, chat_id, message): """Send message to Feishu chat""" url = "https://open.feishu.cn/open-apis/im/v1/messages" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } params = {"receive_id_type": "chat_id"} data = {"receive_id": chat_id, "msg_type": "text", "content": json.dumps({"text": message})} resp = requests.post(url, headers=headers, params=params, json=data, timeout=30) result = resp.json() if result.get("code") == 0: print("Message sent successfully") return True print(f"Failed to send message: {result}") return False def get_github_token(): """Get GitHub token from bashrc or environment""" token = os.environ.get("GITHUB_TOKEN", "") if token: return token # Try to read from gh cli try: result = subprocess.run( ["gh", "auth", "token"], capture_output=True, text=True, timeout=10 ) if result.returncode == 0: return result.stdout.strip() except Exception as e: # Best-effort: gh may not be installed or authenticated print(f"Failed to get gh token: {e}") return "" def get_open_prs(github_token): """Get all open PRs from the repository""" url = f"https://api.github.com/repos/{REPO}/pulls?state=open&per_page=100" headers = { "Authorization": f"Bearer {github_token}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } resp = requests.get(url, headers=headers, timeout=30) if resp.status_code != 200: print(f"Failed to get PRs: {resp.status_code} - {resp.text}") return [] return resp.json() def check_stale_prs(prs, stale_hours=6): """Find PRs that have been open for more than stale_hours""" now = datetime.now() stale_prs = [] for pr in prs: created_at = datetime.strptime(pr["created_at"], "%Y-%m-%dT%H:%M:%SZ") hours_open = (now - created_at).total_seconds() / 3600 if hours_open > stale_hours: stale_prs.append({ "number": pr["number"], "title": pr["title"], "url": pr["html_url"], "author": pr["user"]["login"], "created_at": pr["created_at"], "hours_open": round(hours_open, 1) }) return stale_prs def format_message(stale_prs): """Format reminder message for Feishu""" if not stale_prs: return None message = "🔔 **PR Stale Reminder**\n\n" message += f"The following PRs have been open for more than {STALE_HOURS} hours:\n\n" for pr in stale_prs: message += f"• #{pr['number']} {pr['title']}\n" message += f" Author: {pr['author']} | Hours: {pr['hours_open']}h | {pr['url']}\n\n" message += "---\n" message += f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}" return message def main(): """Main entry point""" print("=== PR Stale Monitor ===") print(f"Repository: {REPO}") print(f"Stale threshold: {STALE_HOURS} hours") # Get tokens feishu_token = get_feishu_token() github_token = get_github_token() if not feishu_token: print("ERROR: Failed to get Feishu token") sys.exit(1) if not github_token: print("ERROR: Failed to get GitHub token") sys.exit(1) # Get open PRs print("\nFetching open PRs...") prs = get_open_prs(github_token) print(f"Found {len(prs)} open PRs") # Check for stale PRs stale_prs = check_stale_prs(prs, STALE_HOURS) print(f"Found {len(stale_prs)} stale PRs (>{STALE_HOURS}h)") if not stale_prs: print("No stale PRs found. Exiting.") return # Format and send message message = format_message(stale_prs) if message: print("\nSending reminder to Feishu...") send_feishu_message(feishu_token, FEISHU_CHAT_ID, message) print("Done!") else: print("No message to send.") if __name__ == "__main__": main()