Skip to content

Commit b1a1d63

Browse files
committed
Fix daily workflow failure notifications
1 parent f3bc077 commit b1a1d63

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

.github/scripts/rerun-failed-workflow-jobs.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
95184
def resolve_pr_number(owner: str, repo: str, run: dict) -> int | None:
96185
pull_requests = run.get("pull_requests") or []
97186
if pull_requests:

.github/workflows/rerun-failed-daily-jobs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
permissions:
2222
contents: read
2323
actions: write
24+
issues: write
2425
runs-on: ubuntu-latest
2526
timeout-minutes: 10
2627
if: github.event.workflow_run.conclusion == 'failure'
@@ -29,6 +30,7 @@ jobs:
2930

3031
- name: Rerun eligible failed jobs
3132
env:
33+
CREATE_WORKFLOW_FAILURE_ISSUE: "true"
3234
GH_TOKEN: ${{ github.token }}
3335
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
3436
run: python .github/scripts/rerun-failed-workflow-jobs.py

0 commit comments

Comments
 (0)