Duplicate Bot: Reduce noise (#58074)

Release Notes:

- N/A
This commit is contained in:
Lena authored and GitHub committed 2026-05-29 13:28:35 +00:00
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__":