fix(ci): preserve ECS cleanup on runner API failures

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
This commit is contained in:
discord9
2026-09-08 21:05:10 +08:00
parent c8ace87cf0
commit 34661ea125
6 changed files with 78 additions and 16 deletions
@@ -216,13 +216,15 @@ def github_api(token: str, method: str, path: str, body: dict | None = None) ->
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.loads(response.read().decode("utf-8"))
payload = response.read()
return json.loads(payload.decode("utf-8")) if payload else {}
except urllib.error.HTTPError as error:
# GitHub's error body says exactly why (e.g. "Must have admin rights to
# Repository" for a PAT without the required scope); surface it instead
# of a bare "HTTP Error 403".
# of a bare "HTTP Error 403". This must remain catchable by teardown so
# a runner deregistration failure does not prevent ECS cleanup.
body = error.read().decode("utf-8", "replace")
raise SystemExit(
raise RuntimeError(
f"GitHub API {method} {path} failed: HTTP {error.code}: {body}\n"
"The token comes from the GH_PERSONAL_ACCESS_TOKEN secret; it needs "
"'repo' scope (classic PAT) or 'Administration: write' on the "
@@ -144,11 +144,11 @@ def delete_instance(client, instance_id: str, region_id: str | None = None) -> b
def deregister_runner(token: str, repo: str, runner_name: str) -> bool:
runner = provision.find_runner_by_name(token, repo, runner_name)
if runner is None:
print(f"Runner {runner_name} is not registered", flush=True)
return True
try:
runner = provision.find_runner_by_name(token, repo, runner_name)
if runner is None:
print(f"Runner {runner_name} is not registered", flush=True)
return True
provision.github_api(token, "DELETE", f"/repos/{repo}/actions/runners/{runner['id']}")
print(f"Deregistered runner {runner_name} (id {runner['id']})", flush=True)
return True