Skip to content

Commit 5c5ffbe

Browse files
committed
fix(health): skip Dependabot's workflow where no config remains
GitHub's dynamic "Dependabot Updates" workflow cannot be disabled, and its old runs outlive the .github/dependabot.yml that started them. TechAPI removed that file in #378, yet its cancelled and out-of-disk Dependabot runs kept showing up as problems. When a repository has the dynamic Dependabot workflow but no .github/dependabot.yml, the workflow is now skipped as a leftover. The file is looked up once per repository, only where that workflow exists; a 404 means absent, and any other error keeps the workflow checked.
1 parent 4cbad8d commit 5c5ffbe

2 files changed

Lines changed: 48 additions & 2 deletions

File tree

‎machine/health.py‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
by an environment rule leaves no log at all. Each of those went unnoticed for
77
days. This treats anything other than success/skipped as a problem.
88
9+
One exception: GitHub's dynamic "Dependabot Updates" workflow cannot be
10+
disabled, and its old runs outlive the `.github/dependabot.yml` that started
11+
them. Without that file no version update can run, so in a repository that
12+
has none the workflow is skipped as a leftover.
13+
914
It only reads. The report goes to stdout and the run's step summary; nothing
1015
is written to GitHub and nobody is mentioned, so it runs only when a person
1116
starts it.
@@ -39,6 +44,9 @@
3944
SECRET = re.compile(r"secrets\.(\w+)")
4045
BUILTIN_TOKEN = "GITHUB_TOKEN"
4146

47+
# GitHub's own workflow for Dependabot, present even after its config is gone.
48+
DEPENDABOT_WORKFLOW = "dynamic/dependabot/"
49+
4250

4351
@dataclass
4452
class Finding:
@@ -122,6 +130,26 @@ def workflow_token_findings(repo: str, branch: str, workflow: dict[str, Any],
122130
return [finding] if finding else []
123131

124132

133+
def dependabot_configured(repo: str, token: str | None) -> bool:
134+
"""Whether the repository has `.github/dependabot.yml` on its default branch.
135+
136+
A 404 means it has none. Any other failure counts as configured, so a
137+
lookup that went wrong never hides a real Dependabot failure.
138+
"""
139+
try:
140+
_get(f"{API}/repos/{repo}/contents/.github/dependabot.yml", token)
141+
except urllib.error.HTTPError as exc:
142+
return exc.code != 404
143+
except urllib.error.URLError:
144+
return True
145+
return True
146+
147+
148+
def leftover_workflow(workflow: dict[str, Any], has_dependabot_config: bool) -> bool:
149+
"""Dependabot's dynamic workflow in a repository with no Dependabot config."""
150+
return workflow.get("path", "").startswith(DEPENDABOT_WORKFLOW) and not has_dependabot_config
151+
152+
125153
def check(config: dict[str, Any], token: str | None) -> list[Finding]:
126154
findings: list[Finding] = []
127155
for repo in config["repos"]:
@@ -132,13 +160,17 @@ def check(config: dict[str, Any], token: str | None) -> list[Finding]:
132160
except urllib.error.URLError as exc:
133161
findings.append(Finding(name, f"could not list workflows: {exc}"))
134162
continue
163+
# One lookup per repository, and only if Dependabot's workflow is there.
164+
has_dependabot_config = not any(
165+
w.get("path", "").startswith(DEPENDABOT_WORKFLOW) for w in workflows.get("workflows", [])
166+
) or dependabot_configured(name, token)
135167
for branch in repo["branches"]:
136168
# One query per workflow, not "the last N runs": a weekly job would
137169
# scroll out of any fixed window behind the daily ones, and weekly
138170
# jobs are exactly the ones that fail unnoticed.
139171
runs: list[dict[str, Any]] = []
140172
for workflow in workflows.get("workflows", []):
141-
if workflow.get("state") != "active":
173+
if workflow.get("state") != "active" or leftover_workflow(workflow, has_dependabot_config):
142174
continue
143175
url = (f"{API}/repos/{name}/actions/workflows/{workflow['id']}/runs"
144176
f"?branch={branch}&status=completed&per_page=1")

‎tests/test_health.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from machine.health import endpoint_finding, latest_runs, render, run_findings
5+
from machine.health import endpoint_finding, latest_runs, leftover_workflow, render, run_findings
66

77

88
def _run(name: str, conclusion: str | None, status: str = "completed") -> dict:
@@ -40,6 +40,20 @@ def test_in_progress_runs_are_ignored():
4040
assert latest_runs(runs)["dump"]["conclusion"] == "success"
4141

4242

43+
def test_dependabot_workflow_without_config_is_a_leftover():
44+
# TechAPI removed dependabot.yml; GitHub keeps the workflow and its old runs.
45+
dependabot = {"name": "Dependabot Updates", "path": "dynamic/dependabot/dependabot-updates"}
46+
assert leftover_workflow(dependabot, has_dependabot_config=False)
47+
assert not leftover_workflow(dependabot, has_dependabot_config=True)
48+
49+
50+
def test_other_workflows_are_never_leftovers():
51+
pages = {"name": "pages-build-deployment", "path": "dynamic/pages/pages-build-deployment"}
52+
ci = {"name": "test", "path": ".github/workflows/test.yml"}
53+
assert not leftover_workflow(pages, has_dependabot_config=False)
54+
assert not leftover_workflow(ci, has_dependabot_config=False)
55+
56+
4357
def test_endpoint_without_expected_field_is_a_problem():
4458
assert endpoint_finding("summary", "https://x", "count", {"other": 1})
4559
assert endpoint_finding("summary", "https://x", "count", "<html>404</html>")

0 commit comments

Comments
 (0)