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

@@ -1 +0,0 @@
ci: re-run after routing fix

View File

@@ -16,6 +16,18 @@ on:
jobs:
gate:
runs-on: veron-1
# Run IN a Python 3.12 image rather than trusting the runner image's
# toolchain. The first real CI run wedged here: catthehacker/ubuntu:act-22.04
# ships Python 3.10.12, 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. It churned for 14 minutes
# at 100% CPU with `-q` hiding all of it, and would have churned until the
# runner timeout. A version mismatch presenting as a hang, not an error.
container:
image: python:3.12-bookworm
# And a hard ceiling, so a wedged step is a red check in minutes rather than
# an occupied runner for half an hour.
timeout-minutes: 12
services:
postgres:
image: postgres:16-alpine
@@ -32,8 +44,10 @@ jobs:
- name: install
run: |
python3 --version
python3 -m venv .venv
.venv/bin/pip install -q -e ".[dev]"
.venv/bin/pip install -q --upgrade pip
.venv/bin/pip install -e ".[dev]"
- name: lint
run: .venv/bin/ruff check api scripts

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"