G7.3: pin Python 3.12 in CI and cap job runtime
Some checks failed
check / gate (push) Failing after 7s

The first real CI run wedged for 14 minutes. Not a network problem, not the
isolation work — catthehacker/ubuntu:act-22.04 ships Python 3.10.12 while this
project declares requires-python >=3.12, and pip answered that by backtracking
through the entire release history of every dependency looking for something
3.10-compatible. At 100% CPU, with -q hiding every line of it, and it would
have churned until the runner's 30m timeout.

A version mismatch presenting as a hang rather than an error is worth a test,
so there is one: the workflow's python image must satisfy pyproject's
requires-python, checked by parsing both rather than by eyeballing them.

Also: every job now has timeout-minutes. A wedged step should be a red check in
minutes, not an occupied runner for half an hour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Grant Whitmer
2026-08-12 11:13:39 -04:00
parent ed71102e49
commit 980fc2dddd
3 changed files with 45 additions and 2 deletions

View File

@@ -461,3 +461,33 @@ def test_g75_workflows_use_a_label_this_runner_actually_provides():
continue
label = ln.split("runs-on:")[1].strip()
assert label in provided, f"{wf.name}: '{label}' is not a provided label"
def test_g73_workflow_pins_a_python_that_satisfies_requires_python():
"""The first real CI run wedged for 14 minutes because the runner image
ships Python 3.10 and this project requires 3.12: pip answered by
backtracking through every historical version of every dependency, at full
CPU, silently. A version mismatch presenting as a hang rather than an
error."""
import re as _re
pyproject = (ROOT / "pyproject.toml").read_text()
m = _re.search(r'requires-python\s*=\s*">=(\d+)\.(\d+)"', pyproject)
assert m, "pyproject declares no requires-python"
major, minor = int(m.group(1)), int(m.group(2))
for wf in ROOT.rglob(".gitea/workflows/*.y*ml"):
text = wf.read_text()
img = _re.search(r"image:\s*python:(\d+)\.(\d+)", text)
assert img, f"{wf.name}: job does not pin a python image"
assert (int(img.group(1)), int(img.group(2))) >= (major, minor), (
f"{wf.name}: pins python {img.group(0)} but the project requires "
f">={major}.{minor}"
)
def test_g73_every_job_has_a_timeout():
"""A wedged step should be a red check in minutes, not an occupied runner
for half an hour."""
for wf in ROOT.rglob(".gitea/workflows/*.y*ml"):
assert "timeout-minutes:" in wf.read_text(), f"{wf.name}: no job timeout"