From a4c32729013d1af388bbad4c7c07402c2c710f7b Mon Sep 17 00:00:00 2001 From: Mahdi Shafiei Date: Wed, 30 Sep 2026 12:47:30 -0700 Subject: [PATCH] ci: prune every uv cache before it is saved setup-uv v9 and later default prune-cache to false, so a saved uv cache keeps every wheel uv downloaded. In datarax the unpruned caches reached 3.9 GB each and evicted the dataset cache (avitai/datarax#49). Set prune-cache: true on the one setup-uv step, in the shared setup-diffbio action. A contract test now fails when any setup-uv step in a workflow or a composite action leaves prune-cache out. --- .github/actions/setup-diffbio/action.yml | 1 + tests/test_ci_concurrency.py | 28 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/.github/actions/setup-diffbio/action.yml b/.github/actions/setup-diffbio/action.yml index 0e93b5d..6ac0689 100644 --- a/.github/actions/setup-diffbio/action.yml +++ b/.github/actions/setup-diffbio/action.yml @@ -31,6 +31,7 @@ runs: - name: Install uv uses: astral-sh/setup-uv@v10.2.0 with: + prune-cache: true version: "0.12.21" enable-cache: true cache-suffix: ${{ inputs.cache-suffix }} diff --git a/tests/test_ci_concurrency.py b/tests/test_ci_concurrency.py index bbb348c..1622e49 100644 --- a/tests/test_ci_concurrency.py +++ b/tests/test_ci_concurrency.py @@ -37,3 +37,31 @@ def test_every_workflow_a_push_triggers_cancels_the_run_it_supersedes() -> None: assert isinstance(concurrency, dict), f"{name} declares no concurrency group" assert "github.ref" in str(concurrency.get("group")), f"{name}'s group ignores the ref" assert concurrency.get("cancel-in-progress") is True, f"{name} keeps superseded runs" + + +def test_every_uv_cache_is_pruned_before_it_is_saved() -> None: + """A saved uv cache holds only what uv built, not every wheel it downloaded. + + setup-uv prunes only when asked (``prune-cache`` defaults to false from v9); unpruned, the + caches of the heavy extras grow to gigabytes each and evict the repository's other caches. + """ + github = WORKFLOWS.parents[1] / ".github" + documents = [ + *sorted(github.glob("workflows/*.yml")), + *sorted(github.glob("actions/*/action.yml")), + ] + checked = 0 + unpruned: list[str] = [] + for path in documents: + document = yaml.safe_load(path.read_text(encoding="utf-8")) + owners = {**document.get("jobs", {}), "runs": document.get("runs") or {}} + for owner, body in owners.items(): + for step in body.get("steps", []): + if not str(step.get("uses", "")).startswith("astral-sh/setup-uv@"): + continue + checked += 1 + if (step.get("with") or {}).get("prune-cache") is not True: + unpruned.append(f"{path.relative_to(WORKFLOWS.parents[1])}:{owner}") + + assert checked, "no setup-uv step found; the contract is reading the wrong files" + assert unpruned == [], f"setup-uv steps saving an unpruned cache: {unpruned}"