1 parent
c77fa05432
commit
f2b2b6b19a
2 files changed
+263
-22
No files matched your search
@@ -24,6 +24,7 @@ import functools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import requests
|
||||
@@ -47,6 +48,8 @@ BOT_START_DATE = "2026-02-18"
|
||||
NEEDS_TRIAGE_LABEL = "state:needs triage"
|
||||
DEFAULT_PROJECT_NUMBER = 76
|
||||
VALID_CLOSED_AS_VALUES = {"duplicate", "not_planned", "completed"}
|
||||
# HTTP statuses we'll retry on for GET requests
|
||||
TRANSIENT_HTTP_STATUSES = {429, 500, 502, 503, 504}
|
||||
# Add a new tuple when you deploy a new version of the bot that you want to
|
||||
# keep track of (e.g. the prompt gets a rewrite or the model gets swapped).
|
||||
# Newest first, please. The datetime is for the deployment time (merge to main).
|
||||
@@ -67,10 +70,22 @@ def bot_version_for_time(date_string):
|
||||
|
||||
|
||||
def github_api_get(path, params=None):
|
||||
"""Fetch JSON from the GitHub REST API, retrying transient failures. Raises on non-2xx status."""
|
||||
url = f"{GITHUB_API}/{path.lstrip('/')}"
|
||||
response = requests.get(url, headers=GITHUB_HEADERS, params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
for attempt in range(3):
|
||||
try:
|
||||
response = requests.get(url, headers=GITHUB_HEADERS, params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.RequestException as e:
|
||||
transient = isinstance(e, (requests.ConnectionError, requests.Timeout)) or (
|
||||
isinstance(e, requests.HTTPError) and e.response.status_code in TRANSIENT_HTTP_STATUSES
|
||||
)
|
||||
if not transient or attempt == 2:
|
||||
raise
|
||||
wait = 2 ** attempt
|
||||
print(f" Transient GitHub API error ({e}); retrying in {wait}s")
|
||||
time.sleep(wait)
|
||||
|
||||
|
||||
def github_search_issues(query):
|
||||
@@ -161,9 +176,11 @@ def find_canonical_among(duplicate_number, candidates):
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
# candidate issue numbers are baked into the query body via field aliases
|
||||
# (GraphQL doesn't let you parametrize alias names), so $numbers isn't needed.
|
||||
data = github_api_graphql(
|
||||
"""
|
||||
query($owner: String!, $repo: String!, $numbers: [Int!]!) {
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
PLACEHOLDER
|
||||
}
|
||||
@@ -174,7 +191,7 @@ def find_canonical_among(duplicate_number, candidates):
|
||||
f' nodes {{ ... on MarkedAsDuplicateEvent {{ duplicate {{ ... on Issue {{ number }} }} }} }} }} }}'
|
||||
for number in candidates
|
||||
)),
|
||||
{"owner": REPO_OWNER, "repo": REPO_NAME, "numbers": list(candidates)},
|
||||
{"owner": REPO_OWNER, "repo": REPO_NAME},
|
||||
partial_errors_ok=True,
|
||||
)
|
||||
|
||||
@@ -409,11 +426,10 @@ def classify_as_assist(issue, bot_comment):
|
||||
bot_comment_time=bot_comment["created_at"])
|
||||
return
|
||||
|
||||
original = None
|
||||
try:
|
||||
original = find_canonical_among(issue["number"], suggested)
|
||||
except (requests.RequestException, RuntimeError) as error:
|
||||
print(f" Warning: failed to query candidate timelines: {error}")
|
||||
# Let exceptions from find_canonical_among propagate — a query failure here is
|
||||
# not the same as "no canonical match" and shouldn't be silently downgraded to
|
||||
# a Needs review entry. Failing the workflow surfaces the problem immediately.
|
||||
original = find_canonical_among(issue["number"], suggested)
|
||||
|
||||
if original:
|
||||
status = "Auto-classified"
|
||||
@@ -483,6 +499,8 @@ def classify_open():
|
||||
errors += 1
|
||||
|
||||
print(f" Done: added {added}, skipped {skipped}, errors {errors}")
|
||||
if errors > 0:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user