Skip to content

Commit 8a9522f

Browse files
Merge #7267: fix: prevent self-reinforcing Blacksmith runner selection loop
bf81f26 fix: prevent self-reinforcing Blacksmith runner selection loop (pasta) Pull request description: ## Summary - `count_queued_jobs` was counting ALL queued jobs regardless of runner type, including jobs already routed to Blacksmith - This created a feedback loop: once Blacksmith was selected, its queued jobs inflated the backlog count, causing subsequent runs to also select Blacksmith while GitHub-hosted runners sat idle - Added `targets_github_hosted_runner()` filter to exclude `blacksmith-*` and `self-hosted` labeled jobs from the backlog count ## Test plan - [ ] Verify `test_select_dynamic_runner.py` passes (6 tests including new `test_count_queued_jobs_excludes_blacksmith_jobs`) - [ ] Monitor runner usage after merge to confirm GitHub-hosted runners are utilized when backlog is low - [ ] Verify Blacksmith is still selected when GitHub runner backlog genuinely exceeds threshold 🤖 Generated with [Claude Code](https://claude.com/claude-code) Top commit has no ACKs. Tree-SHA512: 8b3afab62f095f0068cdd1ba4d9d0f27db59fbd1c3d7ee96f48ade20fe342ec077bb5a6bb7df5fd410034bb17331de815e2d7f0593c8e6d884027dc32c616cca
2 parents 9641540 + bf81f26 commit 8a9522f

2 files changed

Lines changed: 54 additions & 5 deletions

File tree

.github/workflows/select_dynamic_runner.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
DEFAULT_RUNNER_AMD64 = "ubuntu-24.04"
2323
DEFAULT_RUNNER_ARM64 = "ubuntu-24.04-arm"
2424
REQUEST_TIMEOUT_SECONDS = 10
25+
# Prefixes — any label starting with one of these indicates a non-GitHub-hosted runner
26+
NON_GITHUB_HOSTED_RUNNER_PREFIXES = ("blacksmith-",)
27+
SELF_HOSTED_LABEL = "self-hosted"
2528

2629

2730
def parse_next_link(link_header: str) -> Optional[str]:
@@ -78,6 +81,19 @@ def iter_pages(
7881
next_url = parse_next_link(headers.get("Link", ""))
7982

8083

84+
def targets_github_hosted_runner(job: Dict) -> bool:
85+
"""Return True if the job targets GitHub-hosted runners, not Blacksmith or self-hosted."""
86+
for label in job.get("labels", []):
87+
if not isinstance(label, str):
88+
continue
89+
if label == SELF_HOSTED_LABEL:
90+
return False
91+
for prefix in NON_GITHUB_HOSTED_RUNNER_PREFIXES:
92+
if label.startswith(prefix):
93+
return False
94+
return True
95+
96+
8197
def count_queued_jobs(
8298
fetch_json: Callable[[str], Tuple[Dict, Dict[str, str]]],
8399
repos: Sequence[str],
@@ -104,7 +120,7 @@ def count_queued_jobs(
104120
).format(repo, run_id)
105121
for payload in iter_pages(fetch_json, jobs_url):
106122
for job in payload.get("jobs", []):
107-
if job.get("status") == "queued":
123+
if job.get("status") == "queued" and targets_github_hosted_runner(job):
108124
queued_jobs += 1
109125

110126
return queued_jobs

.github/workflows/test_select_dynamic_runner.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ def test_count_queued_jobs_deduplicates_runs_across_status_queries(self):
2929
jobs_url: (
3030
{
3131
"jobs": [
32-
{"status": "queued"},
33-
{"status": "queued"},
34-
{"status": "in_progress"},
32+
{"status": "queued", "labels": ["ubuntu-24.04"]},
33+
{"status": "queued", "labels": ["ubuntu-24.04"]},
34+
{"status": "in_progress", "labels": ["ubuntu-24.04"]},
3535
]
3636
},
3737
{},
@@ -43,6 +43,39 @@ def fetch_json(url):
4343

4444
self.assertEqual(MODULE.count_queued_jobs(fetch_json, [repo]), 2)
4545

46+
def test_count_queued_jobs_excludes_blacksmith_jobs(self):
47+
repo = "dashpay/dash"
48+
queued_url = (
49+
"https://api.github.com/repos/{}/actions/runs?status=queued&per_page=100"
50+
).format(repo)
51+
in_progress_url = (
52+
"https://api.github.com/repos/{}/actions/runs?status=in_progress&per_page=100"
53+
).format(repo)
54+
jobs_url = (
55+
"https://api.github.com/repos/{}/actions/runs/101/jobs?per_page=100"
56+
).format(repo)
57+
58+
responses = {
59+
queued_url: ({"workflow_runs": [{"id": 101}]}, {}),
60+
in_progress_url: ({"workflow_runs": []}, {}),
61+
jobs_url: (
62+
{
63+
"jobs": [
64+
{"status": "queued", "labels": ["ubuntu-24.04"]},
65+
{"status": "queued", "labels": ["blacksmith-4vcpu-ubuntu-2404"]},
66+
{"status": "queued", "labels": ["blacksmith-4vcpu-ubuntu-2404-arm"]},
67+
{"status": "queued", "labels": ["self-hosted", "linux"]},
68+
]
69+
},
70+
{},
71+
),
72+
}
73+
74+
def fetch_json(url):
75+
return responses[url]
76+
77+
self.assertEqual(MODULE.count_queued_jobs(fetch_json, [repo]), 1)
78+
4679
def test_label_override_selects_blacksmith_even_with_low_backlog(self):
4780
outputs = MODULE.select_runners(
4881
event_name="pull_request_target",
@@ -74,7 +107,7 @@ def test_backlog_threshold_selects_blacksmith(self):
74107
responses = {
75108
queued_url: ({"workflow_runs": [{"id": 101}]}, {}),
76109
in_progress_url: ({"workflow_runs": []}, {}),
77-
jobs_url: ({"jobs": [{"status": "queued"}] * 11}, {}),
110+
jobs_url: ({"jobs": [{"status": "queued", "labels": ["ubuntu-24.04"]}] * 11}, {}),
78111
}
79112

80113
outputs = MODULE.select_runners(

0 commit comments

Comments
 (0)