From 9b71829a3d65328cfd87995d0a774ef4fa1c16de Mon Sep 17 00:00:00 2001 From: Brian Date: Mon, 13 Jul 2026 16:12:29 -0500 Subject: [PATCH 1/4] BUILD-11937: partition config-uv cache key by workflow name config-uv's GitHub Actions cache key was shared across all workflows in a repo (uv-${runner.os}-${hash}), unlike config-poetry which scopes its key per workflow. Repos with multiple concurrent workflows thrash a single shared uv cache bucket against GitHub's 10GB per-repo cache limit, causing repeated cache misses and re-downloads from Artifactory. Datadog logs show uv re-fetching identical content-hashed wheel URLs up to 184 times over 14 days (avg 69/wheel) vs poetry's max of 4 (avg 2.2/wheel), and uv's Artifactory bandwidth is up 345.7% over 90 days -- the classic signature of a cache that isn't holding. This mirrors config-poetry's existing sanitize-workflow-name + scoped cache key pattern exactly. --- config-uv/action.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/config-uv/action.yml b/config-uv/action.yml index 94612d3e..a4035656 100644 --- a/config-uv/action.yml +++ b/config-uv/action.yml @@ -88,14 +88,22 @@ runs: run: | echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV" + - name: Sanitize workflow name for cache key + id: sanitize_workflow + if: steps.config-uv-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' + shell: bash + env: + WORKFLOW_NAME: ${{ github.workflow }} + run: echo "workflow_name=${WORKFLOW_NAME// /-}" >> "$GITHUB_OUTPUT" + - name: Cache uv dependencies uses: SonarSource/gh-action_cache@4e40632e780e11a8bbe9b721985ab22b42847cc4 # v1.7.2 if: steps.config-uv-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' with: path: ${{ github.workspace }}/${{ inputs.uv-cache-dir }} - key: uv-${{ runner.os }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory), + key: uv-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory), format('{0}/pyproject.toml', inputs.working-directory)) }} - restore-keys: uv-${{ runner.os }}- + restore-keys: uv-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}- - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 if: steps.config-uv-completed.outputs.skip != 'true' From 1d8cff62d4fcb7f6b027db0c819812154521229c Mon Sep 17 00:00:00 2001 From: Gitar Date: Mon, 13 Jul 2026 21:19:21 +0000 Subject: [PATCH 2/4] fix: wrap long line in config-uv/action.yml to satisfy yamllint Co-authored-by: Brian Walsh <20115224+bwalsh434@users.noreply.github.com> --- config-uv/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config-uv/action.yml b/config-uv/action.yml index a4035656..0fc60711 100644 --- a/config-uv/action.yml +++ b/config-uv/action.yml @@ -101,8 +101,8 @@ runs: if: steps.config-uv-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' with: path: ${{ github.workspace }}/${{ inputs.uv-cache-dir }} - key: uv-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory), - format('{0}/pyproject.toml', inputs.working-directory)) }} + key: uv-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}-${{ + hashFiles(format('{0}/uv.lock', inputs.working-directory), format('{0}/pyproject.toml', inputs.working-directory)) }} restore-keys: uv-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}- - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 From 15ca28e3555204a8053e64ac519885e00a2e0407 Mon Sep 17 00:00:00 2001 From: Brian Date: Wed, 15 Jul 2026 18:54:25 -0500 Subject: [PATCH 3/4] BUILD-11964: Harden config-uv dependency cache keys --- README.md | 28 ++++++++++++++++++++++++++++ config-uv/action.yml | 39 +++++++++++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 46825f89..50e4324b 100644 --- a/README.md +++ b/README.md @@ -1280,6 +1280,14 @@ default = true > **Note:** This action automatically calls [`get-build-number`](#get-build-number) to manage the build number. +The cache key is derived from `uv.lock` and `pyproject.toml` in `working-directory`. +When the project is not at the repository root, always set `working-directory` +to the directory that the job installs. The action disables caching with a +warning instead of creating an empty, permanently stale cache key when neither +dependency file exists there. +For a job that intentionally installs several uv projects, set +`cache-dependency-glob` to a glob that covers every lockfile it installs. + ### Requirements #### Required GitHub Permissions @@ -1308,6 +1316,25 @@ steps: - run: jf uv sync ``` +For a project in a monorepo: + +```yaml +- uses: SonarSource/ci-github-actions/config-uv@v1 + with: + working-directory: services/my-service +- run: jf uv sync + working-directory: services/my-service +``` + +For a job that installs every uv project in a monorepo: + +```yaml +- uses: SonarSource/ci-github-actions/config-uv@v1 + with: + cache-dependency-glob: '**/uv.lock' +- run: ./scripts/install-all-uv-projects.sh +``` + For build-info collection, pass `--build-name` and `--build-number` to `jf uv` and publish with `jf rt build-publish`. ### Inputs @@ -1319,6 +1346,7 @@ For build-info collection, pass `--build-name` and `--build-number` to `jf uv` a | `uv-index-name` | Name of the uv index in `pyproject.toml` to authenticate | `repox` | | `repox-url` | URL for Repox | `https://repox.jfrog.io` | | `uv-cache-dir` | Path to the uv cache directory, relative to GitHub workspace | `.cache/uv` | +| `cache-dependency-glob` | Dependency-file glob for jobs that install multiple uv projects | (optional) | | `disable-caching` | Whether to disable uv caching entirely | `false` | ### Outputs diff --git a/config-uv/action.yml b/config-uv/action.yml index 0fc60711..028c664a 100644 --- a/config-uv/action.yml +++ b/config-uv/action.yml @@ -18,6 +18,9 @@ inputs: uv-cache-dir: description: Path to the uv cache directory, relative to GitHub workspace default: .cache/uv + cache-dependency-glob: + description: Glob of dependency files for jobs that install more than one uv project + default: '' disable-caching: description: Whether to disable uv caching entirely default: 'false' @@ -88,22 +91,42 @@ runs: run: | echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV" - - name: Sanitize workflow name for cache key - id: sanitize_workflow + - name: Resolve uv cache key + id: uv-cache-key if: steps.config-uv-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' shell: bash env: - WORKFLOW_NAME: ${{ github.workflow }} - run: echo "workflow_name=${WORKFLOW_NAME// /-}" >> "$GITHUB_OUTPUT" + CACHE_DEPENDENCY_GLOB: ${{ inputs.cache-dependency-glob }} + DIRECTORY_DEPENDENCY_HASH: + ${{ hashFiles(format('{0}/uv.lock', inputs.working-directory), + format('{0}/pyproject.toml', inputs.working-directory)) }} + GLOB_DEPENDENCY_HASH: ${{ hashFiles(inputs.cache-dependency-glob) }} + WORKING_DIRECTORY: ${{ inputs.working-directory }} + run: | + dependency_hash="$DIRECTORY_DEPENDENCY_HASH" + dependency_source="working-directory '${WORKING_DIRECTORY}'" + if [[ -n "$CACHE_DEPENDENCY_GLOB" ]]; then + dependency_hash="$GLOB_DEPENDENCY_HASH" + dependency_source="cache-dependency-glob '${CACHE_DEPENDENCY_GLOB}'" + fi + if [[ -z "$dependency_hash" ]]; then + message="No dependency files matched ${dependency_source}. " + message+="Point config-uv at the uv project or dependency files that the job installs." + echo "::warning title=uv caching disabled::$message" + echo "cache_enabled=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "cache_enabled=true" >> "$GITHUB_OUTPUT" + echo "dependency_hash=$dependency_hash" >> "$GITHUB_OUTPUT" - name: Cache uv dependencies uses: SonarSource/gh-action_cache@4e40632e780e11a8bbe9b721985ab22b42847cc4 # v1.7.2 - if: steps.config-uv-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' + if: steps.config-uv-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' && + steps.uv-cache-key.outputs.cache_enabled == 'true' with: path: ${{ github.workspace }}/${{ inputs.uv-cache-dir }} - key: uv-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}-${{ - hashFiles(format('{0}/uv.lock', inputs.working-directory), format('{0}/pyproject.toml', inputs.working-directory)) }} - restore-keys: uv-${{ runner.os }}-${{ steps.sanitize_workflow.outputs.workflow_name }}- + key: uv-${{ runner.os }}-${{ steps.uv-cache-key.outputs.dependency_hash }} + restore-keys: uv-${{ runner.os }}- - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 if: steps.config-uv-completed.outputs.skip != 'true' From 22f96ecd55443a0d8cf7d3a02500f3ac26b154de Mon Sep 17 00:00:00 2001 From: Brian Date: Wed, 15 Jul 2026 19:01:16 -0500 Subject: [PATCH 4/4] BUILD-11964: Keep config-uv documentation unchanged --- README.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/README.md b/README.md index 50e4324b..46825f89 100644 --- a/README.md +++ b/README.md @@ -1280,14 +1280,6 @@ default = true > **Note:** This action automatically calls [`get-build-number`](#get-build-number) to manage the build number. -The cache key is derived from `uv.lock` and `pyproject.toml` in `working-directory`. -When the project is not at the repository root, always set `working-directory` -to the directory that the job installs. The action disables caching with a -warning instead of creating an empty, permanently stale cache key when neither -dependency file exists there. -For a job that intentionally installs several uv projects, set -`cache-dependency-glob` to a glob that covers every lockfile it installs. - ### Requirements #### Required GitHub Permissions @@ -1316,25 +1308,6 @@ steps: - run: jf uv sync ``` -For a project in a monorepo: - -```yaml -- uses: SonarSource/ci-github-actions/config-uv@v1 - with: - working-directory: services/my-service -- run: jf uv sync - working-directory: services/my-service -``` - -For a job that installs every uv project in a monorepo: - -```yaml -- uses: SonarSource/ci-github-actions/config-uv@v1 - with: - cache-dependency-glob: '**/uv.lock' -- run: ./scripts/install-all-uv-projects.sh -``` - For build-info collection, pass `--build-name` and `--build-number` to `jf uv` and publish with `jf rt build-publish`. ### Inputs @@ -1346,7 +1319,6 @@ For build-info collection, pass `--build-name` and `--build-number` to `jf uv` a | `uv-index-name` | Name of the uv index in `pyproject.toml` to authenticate | `repox` | | `repox-url` | URL for Repox | `https://repox.jfrog.io` | | `uv-cache-dir` | Path to the uv cache directory, relative to GitHub workspace | `.cache/uv` | -| `cache-dependency-glob` | Dependency-file glob for jobs that install multiple uv projects | (optional) | | `disable-caching` | Whether to disable uv caching entirely | `false` | ### Outputs