1414import shutil
1515import subprocess
1616import sys
17+ import time
1718from concurrent .futures import ThreadPoolExecutor
1819from dataclasses import dataclass
1920from pathlib import Path
2930VERSION_RE = re .compile (r'val stableVersion = "(\d+\.\d+\.\d+)' )
3031ISSUE_REF_RE = re .compile (r"(?:issues|pull)/(\d+)|(?<![A-Za-z0-9/])#(\d+)\b" )
3132GH_FETCH_WORKERS = 8
33+ GH_FETCH_RETRIES = 3
34+ GH_FETCH_RETRY_DELAY = 5.0
3235
3336
3437@dataclass
@@ -103,6 +106,25 @@ def load_json(args: list[str]) -> Any:
103106 return json .loads (result .stdout )
104107
105108
109+ def load_json_with_retry (args : list [str ]) -> Any :
110+ """Run a `gh` command with retries to absorb transient API failures."""
111+ last_error : subprocess .CalledProcessError | None = None
112+ for attempt in range (1 , GH_FETCH_RETRIES + 1 ):
113+ try :
114+ return load_json (args )
115+ except subprocess .CalledProcessError as e :
116+ last_error = e
117+ if attempt == GH_FETCH_RETRIES :
118+ break
119+ warn (
120+ f"gh command failed (attempt { attempt } /{ GH_FETCH_RETRIES } ): "
121+ f"{ ' ' .join (args )} \n stderr: { e .stderr } "
122+ )
123+ time .sleep (GH_FETCH_RETRY_DELAY * attempt )
124+ assert last_error is not None
125+ raise last_error
126+
127+
106128def warn (message : str ) -> None :
107129 print (message , file = sys .stderr )
108130
@@ -411,7 +433,7 @@ def fetch_parallel(
411433
412434
413435def fetch_pr_data (pr_number : int ) -> dict [str , Any ]:
414- return load_json (
436+ return load_json_with_retry (
415437 [
416438 "gh" ,
417439 "pr" ,
@@ -426,7 +448,7 @@ def fetch_pr_data(pr_number: int) -> dict[str, Any]:
426448
427449
428450def fetch_ref_data (ref_number : int ) -> dict [str , Any ]:
429- return load_json (
451+ return load_json_with_retry (
430452 [
431453 "gh" ,
432454 "issue" ,
0 commit comments