Install uv without an action so the matrix jobs stop racing
CI / shell (shellcheck + syntax) (push) Failing after 1s
CI / python 3.11 (push) Failing after 1s
CI / python 3.12 (push) Failing after 1s
CI / python 3.13 (push) Failing after 0s

act caches each action as one shared git clone under /root/.cache/act/<hash>
and re-pulls it per job. The three python matrix jobs start within the same
second on the self-hosted runner, race on that directory, and the loser dies
with "lstat /root/.cache/act/<hash>/.npmrc: no such file or directory" before
any test runs — a red main with zero suite output and a different victim each
push (3.12, then 3.11).

The action was only fetching a binary; the matrix interpreter is selected per
command by uvx --python. A run: step has no action-cache entry and cannot
race, and keeps the jobs parallel — serialising the matrix would cost 3x the
wall clock and still leave actions/checkout shared across four jobs. The uv
version is pinned under the same rule as ruff.

The accompanying test parses uses: directives rather than the raw text, so the
comment can still name the action it avoids, and uses re instead of PyYAML
because CI runs uvx pytest, whose environment holds pytest and nothing else.
This commit is contained in:
2026-08-26 05:28:26 +00:00
parent 5f8d42f2cf
commit 02ba653127
3 changed files with 81 additions and 1 deletions
+40
View File
@@ -172,3 +172,43 @@ class TestCompatCannotSilentlyPass:
with open(os.path.join(WORKFLOWS, "compat.yml"), encoding="utf-8") as fh:
src = fh.read()
assert "steps.check.outputs.shipped_broken != '0'" in src
class TestActionCacheRace:
"""act caches each ACTION as one shared clone and re-pulls it per job.
Matrix jobs start within the same second on the self-hosted Gitea runner, so
they race on `/root/.cache/act/<hash>` and the loser dies with `lstat
.../<file>: no such file or directory` before any test runs -- a red `main`
with zero suite output and a different victim each push. Fewer actions in a
fan-out job means fewer directories to race on.
"""
def test_uv_is_installed_without_an_action(self):
"""Checks `uses:` directives, not prose.
The comment in ci.yml names the action it deliberately avoids, and that
explanation is the most useful thing in the file -- a test that greps the
raw text would forbid documenting the very lesson it enforces. Parsed
with a regex rather than PyYAML on purpose: CI runs `uvx pytest`, whose
environment holds pytest and nothing else, so a third-party import here
fails on the runner while passing locally.
"""
with open(os.path.join(WORKFLOWS, "ci.yml"), encoding="utf-8") as fh:
ci = fh.read()
used = re.findall(r"^\s*-?\s*uses:\s*(\S+)", ci, re.M)
assert not [u for u in used if "setup-uv" in u], (
"installing uv via an action reintroduces the act action-cache race "
"that turned main red on two of three pushes; install it in a run: step"
)
assert "astral.sh/uv/" in ci
def test_the_uv_version_is_pinned(self):
with open(os.path.join(WORKFLOWS, "ci.yml"), encoding="utf-8") as fh:
ci = fh.read()
assert re.search(r'UV_VERSION:\s*"\d+\.\d+\.\d+"', ci), (
"an unpinned uv lets any upstream release turn main red with no "
"code change here -- the same rule ruff is pinned under"
)
# The version must be used, not just declared.
assert 'https://astral.sh/uv/${UV_VERSION}/install.sh' in ci