Let only Test save the shared uv cache - #540
Merged
Merged
Conversation
astral-sh/setup-uv derives its cache key from arch, runner, Python
version and the dependency hash, so every job running the same OS with
the same Python version lands on the same key. When one push starts
several such jobs, they all try to reserve that key at save time; the
first to finish wins and the rest log
Failed to save: Unable to reserve cache with key setup-uv-2-...,
another job may be creating this cache.
The failure is save-time only and harmless: the losing job would have
written identical content, and every job still restores the cache
normally. It is log noise.
Giving each workflow its own cache via cache-suffix would silence it
too, but that stores the same bytes several times over and was
rejected in audeering/audeer#207 in favour of a single saver.
Documentation, Linter and Publish therefore stop saving; Test keeps
the default and is the sole writer of the shared key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Reviewer's guide (collapsed on small PRs)Reviewer's GuideConfigure GitHub Actions workflows so that only the Test workflow writes the shared uv cache by disabling cache saving in Documentation, Linter, and Publish workflows while keeping setup-uv usage otherwise unchanged. Sequence diagram for GitHub workflows using setup-uv with selective cache savingsequenceDiagram
actor DocWorkflow
actor LinterWorkflow
actor PublishWorkflow
actor TestWorkflow
participant SetupUv
DocWorkflow->>SetupUv: setup-uv(save-cache=false)
LinterWorkflow->>SetupUv: setup-uv(save-cache=false)
PublishWorkflow->>SetupUv: setup-uv(save-cache=false)
TestWorkflow->>SetupUv: setup-uv(default save-cache)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
The Test matrix declares ubuntu-latest with Python 3.10 twice -- once plain, once with requirements: 'minimum'. Same OS and same Python version means the same setup-uv cache key, so those two legs race with each other inside Test, where the previous commit cannot reach them. Measured twice on this repo, both times on a run whose keys were fresh: main 2e77f55, run 30999457855 build (ubuntu-latest, 3.10) saved 3.10.20 build (ubuntu-latest, 3.10, minimum) Failed to save: Unable to reserve cache with key setup-uv-2-...-3.10.20-... PR #539 a577fa2, run 30996727386 same pair, same key, same warning -- the loser was again the minimum leg Order is not fixed: on the 2026-05-12 push to main the minimum leg won and the plain leg lost. The plain leg is the natural saver, since the minimum leg downgrades several dependencies after uv sync, so let the variant stop saving. GitHub renders the expression as true or false, which the boolean input accepts. Note that the run on this branch could not reproduce the warning: every key was already present in the main scope, so setup-uv reported "Cache hit occurred on key ..., not saving cache." and no job attempted a save. The race only surfaces on the first run after a key rotation, which is exactly what the two runs cited above were. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adjusts GitHub Actions astral-sh/setup-uv caching behavior to avoid cross-job cache save collisions (and resulting “Unable to reserve cache…” warning annotations) by ensuring only the intended job(s) attempt to save the shared uv cache.
Changes:
- Disable
setup-uvcache saving in Documentation, Linter, and Publish workflows (save-cache: false). - In Test workflow, disable cache saving only for the
requirements: minimummatrix leg to prevent intra-workflow collisions while keeping the non-minimum leg as the saver.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
.github/workflows/test.yml |
Prevents the minimum matrix leg from saving the uv cache to avoid collisions with the non-minimum leg. |
.github/workflows/publish.yml |
Disables uv cache saving so Publish no longer races other workflows to save the same key. |
.github/workflows/linter.yml |
Disables uv cache saving so Linter no longer races other workflows to save the same key. |
.github/workflows/doc.yml |
Disables uv cache saving so Documentation no longer races other workflows to save the same key. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
hagenw
approved these changes
Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
astral-sh/setup-uvbuilds its cache key from arch, runner image, Python version and the dependency hash:Nothing in that key identifies the workflow or the job. Every job that runs the same OS with the same Python version therefore derives the same key, and when one push starts several such jobs they all try to reserve it at save time. The first to finish wins; the rest end with a warning annotation:
This is save-time only and harmless — the losing job would have written identical content, and all jobs still restore the cache normally. It is log noise, and this repo has it.
The newest push to
main(2e77f55) shows both collision groups:…-3.12.13-…builduv cache saved with key: …— won…-3.12.13-…build (ubuntu-latest, 3.12)…-3.12.13-…build (ubuntu-latest, 3.12)…-3.10.20-…build (ubuntu-latest, 3.10)uv cache saved with key: …— won…-3.10.20-…build (ubuntu-latest, 3.10, minimum)Measured frequency: 3 of the 10 jobs on that push, all three annotated. The race is not new — it also hit 3 of 11 jobs on the previous push, a672f8c (Documentation, Test 3.12, Test 3.10) — but that push still pinned a pre-v9
setup-uv, which logged the same sentence as a plain info line rather than##[warning], so it produced no annotation. Raisingsetup-uvtov9.0.0in 2e77f55 is what made a long-standing race visible. So: 6 of the 21 jobs across the two pushes whose logs are still retained; logs for the four pushes before those have expired (HTTP 410).It reproduces on pull requests too. #539 (a577fa2, run 30996727386) annotated 3 jobs with the same message, including the same 3.10 pair.
One thing worth knowing about the timing:
setup-uvdoes not re-save on an exact key hit — it logsCache hit occurred on key …, not saving cache.So the race fires only on the first run after a key rotation, when every job misses and every job tries to create the key. A dependency change, a new Python patch release, a new runner image or asetup-uvkey-format bump each trigger one.Fix
Keep the one shared key and make exactly one writer.
setup-uvv9.0.0 has asave-cacheinput (defaulttrue).Documentation, Linter and Publish set
save-cache: false. Test keeps the default and is the sole saver.That is not sufficient here, because this repo's Test matrix declares
ubuntu-latest+ Python3.10twice — once plain, once withrequirements: 'minimum'. Same OS and same Python version means the same key, so those two legs race with each other inside Test, which step 1 cannot reach. Both cited runs show exactly that: the minimum leg lost to the plain leg on the…3.10.20…key. So the variant leg stops saving as well:The plain leg is the natural saver — the minimum leg downgrades several dependencies after
uv sync. GitHub renders the expression astrue/false, which the boolean input accepts.Not fixed here: the separate
emodb-srccache (actions/cache, deliberately constant key) races the same way across jobs. It is logged without##[warning], so it raises no annotation, and it is left alone to keep this rollout identical across repos.Trade-off
After a cache-key rotation the non-saving jobs — Documentation, Linter, Publish and the
minimumTest leg — find no cache on the first run and populate their uv cache from the network once, until the Test leg that still saves has written the new key. That is a second or two per job, on one run.Context
Ports audeering/audeer#207 (merged, approved). Adding a
cache-suffixper workflow would silence the warning too, but it stores the same bytes several times over; that design was explicitly rejected there in favour of a single saver.opensmile-pythonandaudbget the same treatment. This repo'smainnever got the fix because it merged before the warning was diagnosed.Test plan
Unable to reserve cacheannotations across every check-run of the head SHAWorth stating plainly: a green run on this branch is necessary but not sufficient proof. Its keys already exist in the
mainscope, so every job gets an exact hit, skips saving, and cannot race no matter what these workflows say. The evidence that the fix is aimed at the right jobs comes from the two cache-miss runs cited above, not from this branch. The first push tomainafter the next key rotation is the run that will confirm it.