@@ -60,16 +60,22 @@ def parse_args():
6060 )
6161 parser .add_argument ("--pr" , default = "auto" , help = "auto, PR number, or PR URL" )
6262 parser .add_argument ("--repo" , help = "Optional OWNER/REPO override" )
63- parser .add_argument ("--poll-seconds" , type = int , default = 30 , help = "Watch poll interval" )
63+ parser .add_argument (
64+ "--poll-seconds" , type = int , default = 30 , help = "Watch poll interval"
65+ )
6466 parser .add_argument (
6567 "--max-flaky-retries" ,
6668 type = int ,
6769 default = 3 ,
6870 help = "Max rerun cycles per head SHA before stop recommendation" ,
6971 )
7072 parser .add_argument ("--state-file" , help = "Path to state JSON file" )
71- parser .add_argument ("--once" , action = "store_true" , help = "Emit one snapshot and exit" )
72- parser .add_argument ("--watch" , action = "store_true" , help = "Continuously emit JSONL snapshots" )
73+ parser .add_argument (
74+ "--once" , action = "store_true" , help = "Emit one snapshot and exit"
75+ )
76+ parser .add_argument (
77+ "--watch" , action = "store_true" , help = "Continuously emit JSONL snapshots"
78+ )
7379 parser .add_argument (
7480 "--retry-failed-now" ,
7581 action = "store_true" ,
@@ -128,7 +134,9 @@ def gh_json(args, repo=None):
128134 try :
129135 return json .loads (raw )
130136 except json .JSONDecodeError as err :
131- raise GhCommandError (f"Failed to parse JSON from gh output for { ' ' .join (args )} " ) from err
137+ raise GhCommandError (
138+ f"Failed to parse JSON from gh output for { ' ' .join (args )} "
139+ ) from err
132140
133141
134142def parse_pr_spec (pr_spec ):
@@ -210,6 +218,8 @@ def extract_repo_from_pr_view(data):
210218 if owner and name :
211219 return f"{ owner } /{ name } "
212220 return None
221+
222+
213223def extract_repo_from_pr_url (pr_url ):
214224 parsed = urlparse (pr_url )
215225 parts = [p for p in parsed .path .split ("/" ) if p ]
@@ -242,7 +252,9 @@ def load_state(path):
242252def save_state (path , state ):
243253 path .parent .mkdir (parents = True , exist_ok = True )
244254 payload = json .dumps (state , indent = 2 , sort_keys = True ) + "\n "
245- fd , tmp_name = tempfile .mkstemp (prefix = f"{ path .name } ." , suffix = ".tmp" , dir = path .parent )
255+ fd , tmp_name = tempfile .mkstemp (
256+ prefix = f"{ path .name } ." , suffix = ".tmp" , dir = path .parent
257+ )
246258 tmp_path = Path (tmp_name )
247259 try :
248260 with os .fdopen (fd , "w" , encoding = "utf-8" ) as tmp_file :
@@ -304,7 +316,16 @@ def summarize_checks(checks):
304316def get_workflow_runs_for_sha (repo , head_sha ):
305317 endpoint = f"repos/{ repo } /actions/runs"
306318 data = gh_json (
307- ["api" , endpoint , "-X" , "GET" , "-f" , f"head_sha={ head_sha } " , "-f" , "per_page=100" ],
319+ [
320+ "api" ,
321+ endpoint ,
322+ "-X" ,
323+ "GET" ,
324+ "-f" ,
325+ f"head_sha={ head_sha } " ,
326+ "-f" ,
327+ "per_page=100" ,
328+ ],
308329 repo = repo ,
309330 )
310331 if not isinstance (data , dict ):
@@ -334,7 +355,12 @@ def failed_runs_from_workflow_runs(runs, head_sha):
334355 "html_url" : str (run .get ("html_url" ) or "" ),
335356 }
336357 )
337- failed_runs .sort (key = lambda item : (str (item .get ("workflow_name" ) or "" ), str (item .get ("run_id" ) or "" )))
358+ failed_runs .sort (
359+ key = lambda item : (
360+ str (item .get ("workflow_name" ) or "" ),
361+ str (item .get ("run_id" ) or "" ),
362+ )
363+ )
338364 return failed_runs
339365
340366
@@ -361,7 +387,10 @@ def failed_jobs_from_workflow_runs(repo, runs, head_sha):
361387 continue
362388 run_status = str (run .get ("status" ) or "" )
363389 run_conclusion = str (run .get ("conclusion" ) or "" )
364- if run_status .lower () == "completed" and run_conclusion not in FAILED_RUN_CONCLUSIONS :
390+ if (
391+ run_status .lower () == "completed"
392+ and run_conclusion not in FAILED_RUN_CONCLUSIONS
393+ ):
365394 continue
366395 jobs = get_jobs_for_run (repo , run_id )
367396 for job in jobs :
@@ -401,7 +430,9 @@ def failed_jobs_from_workflow_runs(repo, runs, head_sha):
401430def get_authenticated_login ():
402431 data = gh_json (["api" , "user" ])
403432 if not isinstance (data , dict ) or not data .get ("login" ):
404- raise GhCommandError ("Unable to determine authenticated GitHub login from `gh api user`" )
433+ raise GhCommandError (
434+ "Unable to determine authenticated GitHub login from `gh api user`"
435+ )
405436 return str (data ["login" ])
406437
407438
@@ -492,7 +523,9 @@ def normalize_reviews(items):
492523 "id" : str (item .get ("id" ) or "" ),
493524 "author" : extract_login (item .get ("user" )),
494525 "author_association" : str (item .get ("author_association" ) or "" ),
495- "created_at" : str (item .get ("submitted_at" ) or item .get ("created_at" ) or "" ),
526+ "created_at" : str (
527+ item .get ("submitted_at" ) or item .get ("created_at" ) or ""
528+ ),
496529 "body" : str (item .get ("body" ) or "" ),
497530 "path" : None ,
498531 "line" : None ,
@@ -535,7 +568,9 @@ def fetch_new_review_items(pr, state, fresh_state, authenticated_login=None):
535568 endpoints = comment_endpoints (repo , pr_number )
536569
537570 issue_payload = gh_api_list_paginated (endpoints ["issue_comment" ], repo = repo )
538- review_comment_payload = gh_api_list_paginated (endpoints ["review_comment" ], repo = repo )
571+ review_comment_payload = gh_api_list_paginated (
572+ endpoints ["review_comment" ], repo = repo
573+ )
539574 review_payload = gh_api_list_paginated (endpoints ["review" ], repo = repo )
540575
541576 issue_items = normalize_issue_comments (issue_payload )
@@ -545,7 +580,9 @@ def fetch_new_review_items(pr, state, fresh_state, authenticated_login=None):
545580 if isinstance (item , dict ) and item .get ("id" ) not in (None , "" )
546581 }
547582 pending_review_ids = {
548- review_id for review_id , review_state in review_states .items () if review_state == "PENDING"
583+ review_id
584+ for review_id , review_state in review_states .items ()
585+ if review_state == "PENDING"
549586 }
550587 pending_review_comment_ids = {
551588 str (item .get ("id" ))
@@ -554,7 +591,9 @@ def fetch_new_review_items(pr, state, fresh_state, authenticated_login=None):
554591 and item .get ("id" ) not in (None , "" )
555592 and str (item .get ("pull_request_review_id" ) or "" ) in pending_review_ids
556593 }
557- review_comment_items = normalize_review_comments (review_comment_payload , review_states )
594+ review_comment_items = normalize_review_comments (
595+ review_comment_payload , review_states
596+ )
558597 review_items = normalize_reviews (review_payload )
559598 all_items = issue_items + review_comment_items + review_items
560599
@@ -598,7 +637,13 @@ def fetch_new_review_items(pr, state, fresh_state, authenticated_login=None):
598637 elif kind == "review" :
599638 seen_review .add (item_id )
600639
601- new_items .sort (key = lambda item : (item .get ("created_at" ) or "" , item .get ("kind" ) or "" , item .get ("id" ) or "" ))
640+ new_items .sort (
641+ key = lambda item : (
642+ item .get ("created_at" ) or "" ,
643+ item .get ("kind" ) or "" ,
644+ item .get ("id" ) or "" ,
645+ )
646+ )
602647 state ["seen_issue_comment_ids" ] = sorted (seen_issue )
603648 state ["seen_review_comment_ids" ] = sorted (seen_review_comment )
604649 state ["seen_review_ids" ] = sorted (seen_review )
@@ -650,7 +695,15 @@ def is_pr_ready_to_merge(pr, checks_summary, new_review_items):
650695 return True
651696
652697
653- def recommend_actions (pr , checks_summary , failed_runs , failed_jobs , new_review_items , retries_used , max_retries ):
698+ def recommend_actions (
699+ pr ,
700+ checks_summary ,
701+ failed_runs ,
702+ failed_jobs ,
703+ new_review_items ,
704+ retries_used ,
705+ max_retries ,
706+ ):
654707 actions = []
655708 if pr ["closed" ] or pr ["merged" ]:
656709 if new_review_items :
@@ -671,7 +724,11 @@ def recommend_actions(pr, checks_summary, failed_runs, failed_jobs, new_review_i
671724 actions .append ("stop_exhausted_retries" )
672725 else :
673726 actions .append ("diagnose_ci_failure" )
674- if checks_summary ["all_terminal" ] and failed_runs and retries_used < max_retries :
727+ if (
728+ checks_summary ["all_terminal" ]
729+ and failed_runs
730+ and retries_used < max_retries
731+ ):
675732 actions .append ("retry_failed_checks" )
676733
677734 if not actions :
@@ -681,7 +738,9 @@ def recommend_actions(pr, checks_summary, failed_runs, failed_jobs, new_review_i
681738
682739def collect_snapshot (args ):
683740 pr = resolve_pr (args .pr , repo_override = args .repo )
684- state_path = Path (args .state_file ) if args .state_file else default_state_file_for (pr )
741+ state_path = (
742+ Path (args .state_file ) if args .state_file else default_state_file_for (pr )
743+ )
685744 state , fresh_state = load_state (state_path )
686745
687746 if not state .get ("started_at" ):
@@ -703,7 +762,9 @@ def collect_snapshot(args):
703762 checks_summary = summarize_checks (checks )
704763 workflow_runs = get_workflow_runs_for_sha (pr ["repo" ], pr ["head_sha" ])
705764 failed_runs = failed_runs_from_workflow_runs (workflow_runs , pr ["head_sha" ])
706- failed_jobs = failed_jobs_from_workflow_runs (pr ["repo" ], workflow_runs , pr ["head_sha" ])
765+ failed_jobs = failed_jobs_from_workflow_runs (
766+ pr ["repo" ], workflow_runs , pr ["head_sha" ]
767+ )
707768
708769 retries_used = current_retry_count (state , pr ["head_sha" ])
709770 actions = recommend_actions (
@@ -845,11 +906,10 @@ def run_watch(args):
845906 },
846907 )
847908 actions = set (snapshot .get ("actions" ) or [])
848- if (
849- "stop_pr_closed" in actions
850- or "stop_exhausted_retries" in actions
851- ):
852- print_event ("stop" , {"actions" : snapshot .get ("actions" ), "pr" : snapshot .get ("pr" )})
909+ if "stop_pr_closed" in actions or "stop_exhausted_retries" in actions :
910+ print_event (
911+ "stop" , {"actions" : snapshot .get ("actions" ), "pr" : snapshot .get ("pr" )}
912+ )
853913 return 0
854914
855915 current_change_key = snapshot_change_key (snapshot )
0 commit comments