@@ -28,6 +28,8 @@ def main() -> None:
2828 rerun_attempts = run ["run_attempt" ] - 1
2929 if rerun_attempts >= max_rerun_attempts :
3030 print (f"Skipped { label } : already rerun { rerun_attempts } times." )
31+ if workflow_failure_issue_enabled ():
32+ open_issue_if_missing (owner , repo , run )
3133 return
3234
3335 ignored_job_suffixes = tuple (
@@ -54,6 +56,8 @@ def main() -> None:
5456 f"Skipped { label } : { len (failed_real_jobs )} failed jobs"
5557 f" exceeded limit { max_failed_jobs } ."
5658 )
59+ if workflow_failure_issue_enabled ():
60+ open_issue_or_add_comment (owner , repo , run )
5761 return
5862
5963 subprocess .run (
@@ -92,6 +96,91 @@ def gh_get(path: str, query: dict[str, str] | None = None):
9296 return json .loads (result .stdout ) if result .stdout .strip () else {}
9397
9498
99+ def workflow_failure_issue_enabled () -> bool :
100+ return os .getenv ("CREATE_WORKFLOW_FAILURE_ISSUE" ) == "true"
101+
102+
103+ def open_issue_if_missing (owner : str , repo : str , run : dict ) -> None :
104+ if find_workflow_issue_number (owner , repo , run ) is not None :
105+ return
106+ create_workflow_issue (owner , repo , run )
107+
108+
109+ def open_issue_or_add_comment (owner : str , repo : str , run : dict ) -> None :
110+ number = find_workflow_issue_number (owner , repo , run )
111+ if number is None :
112+ create_workflow_issue (owner , repo , run )
113+ else :
114+ subprocess .run (
115+ [
116+ "gh" ,
117+ "issue" ,
118+ "comment" ,
119+ str (number ),
120+ "--repo" ,
121+ f"{ owner } /{ repo } " ,
122+ "--body" ,
123+ workflow_issue_body (owner , repo , run ),
124+ ],
125+ check = True ,
126+ )
127+
128+
129+ def find_workflow_issue_number (owner : str , repo : str , run : dict ) -> int | None :
130+ title = workflow_issue_title (run )
131+ result = subprocess .run (
132+ [
133+ "gh" ,
134+ "issue" ,
135+ "list" ,
136+ "--repo" ,
137+ f"{ owner } /{ repo } " ,
138+ "--search" ,
139+ f"in:title { title } " ,
140+ "--limit" ,
141+ "20" ,
142+ "--json" ,
143+ "number,title" ,
144+ ],
145+ capture_output = True ,
146+ text = True ,
147+ check = True ,
148+ )
149+ for issue in json .loads (result .stdout ):
150+ issue_title = issue .get ("title" ) or ""
151+ if issue_title == title or issue_title .startswith (f"{ title } (#" ):
152+ return issue ["number" ]
153+ return None
154+
155+
156+ def create_workflow_issue (owner : str , repo : str , run : dict ) -> None :
157+ subprocess .run (
158+ [
159+ "gh" ,
160+ "issue" ,
161+ "create" ,
162+ "--repo" ,
163+ f"{ owner } /{ repo } " ,
164+ "--title" ,
165+ f"{ workflow_issue_title (run )} (#{ run ['run_number' ]} )" ,
166+ "--body" ,
167+ workflow_issue_body (owner , repo , run ),
168+ ],
169+ check = True ,
170+ )
171+
172+
173+ def workflow_issue_title (run : dict ) -> str :
174+ return f"Workflow failed: { run ['name' ]} "
175+
176+
177+ def workflow_issue_body (owner : str , repo : str , run : dict ) -> str :
178+ return (
179+ f"See [{ run ['name' ]} #{ run ['run_number' ]} ]"
180+ f"(https://github.com/{ owner } /{ repo } /actions/runs/{ run ['id' ]} )."
181+ )
182+
183+
95184def resolve_pr_number (owner : str , repo : str , run : dict ) -> int | None :
96185 pull_requests = run .get ("pull_requests" ) or []
97186 if pull_requests :
0 commit comments