Skip to content

Let only Test save the shared uv cache - #540

Merged
ChristianGeng merged 2 commits into
mainfrom
fix/cache-single-saver
Aug 6, 2026
Merged

Let only Test save the shared uv cache#540
ChristianGeng merged 2 commits into
mainfrom
fix/cache-single-saver

Conversation

@ChristianGeng

@ChristianGeng ChristianGeng commented Aug 6, 2026

Copy link
Copy Markdown
Member

Problem

astral-sh/setup-uv builds its cache key from arch, runner image, Python version and the dependency hash:

setup-uv-2-x86_64-unknown-linux-gnu-ubuntu-24.04-3.12.13-0bf3908...

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:

Failed to save: Unable to reserve cache with key setup-uv-2-x86_64-unknown-linux-gnu-ubuntu-24.04-3.12.13-0bf390890fde43bb03b2b0a7b530c18cf68e9e94bb220b3b9bc8eec54c65d3a2, another job may be creating this cache.

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:

Key (Python) Workflow Job Outcome
…-3.12.13-… Linter build uv cache saved with key: … — won
…-3.12.13-… Documentation build (ubuntu-latest, 3.12) warning
…-3.12.13-… Test build (ubuntu-latest, 3.12) warning
…-3.10.20-… Test build (ubuntu-latest, 3.10) uv cache saved with key: … — won
…-3.10.20-… Test build (ubuntu-latest, 3.10, minimum) warning

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. Raising setup-uv to v9.0.0 in 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-uv does not re-save on an exact key hit — it logs Cache 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 a setup-uv key-format bump each trigger one.

Fix

Keep the one shared key and make exactly one writer. setup-uv v9.0.0 has a save-cache input (default true).

  1. Documentation, Linter and Publish set save-cache: false. Test keeps the default and is the sole saver.

  2. That is not sufficient here, because this repo's Test matrix declares ubuntu-latest + Python 3.10 twice — once plain, once with requirements: '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:

    save-cache: ${{ matrix.requirements != 'minimum' }}

    The plain leg is the natural saver — the minimum leg downgrades several dependencies after uv sync. GitHub renders the expression as true/false, which the boolean input accepts.

Not fixed here: the separate emodb-src cache (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 minimum Test 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-suffix per 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-python and audb get the same treatment. This repo's main never got the fix because it merged before the warning was diagnosed.

Test plan

  • All checks green
  • Zero Unable to reserve cache annotations across every check-run of the head SHA

Worth stating plainly: a green run on this branch is necessary but not sufficient proof. Its keys already exist in the main scope, 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 to main after the next key rotation is the run that will confirm it.

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>
@sourcery-ai

sourcery-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Configure 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 saving

sequenceDiagram
    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)
Loading

File-Level Changes

Change Details Files
Disable saving the shared uv cache in non-Test GitHub Actions workflows while retaining cache restores.
  • Set the setup-uv action input save-cache: false in the Documentation workflow to prevent it from writing the shared cache.
  • Set the setup-uv action input save-cache: false in the Linter workflow to prevent it from writing the shared cache.
  • Set the setup-uv action input save-cache: false in the Publish workflow to prevent it from writing the shared cache.
.github/workflows/doc.yml
.github/workflows/linter.yml
.github/workflows/publish.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-uv cache saving in Documentation, Linter, and Publish workflows (save-cache: false).
  • In Test workflow, disable cache saving only for the requirements: minimum matrix 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.

@ChristianGeng
ChristianGeng requested a review from hagenw August 6, 2026 11:56
@ChristianGeng
ChristianGeng merged commit 218b9f2 into main Aug 6, 2026
14 checks passed
@ChristianGeng
ChristianGeng deleted the fix/cache-single-saver branch August 6, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants