From 11e49bc064289f2f878dcee32d179f4f9846a09c Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Mon, 17 Aug 2026 07:44:08 +0530 Subject: [PATCH] UN-1929 [FIX] Skip uv locking when lockfiles are already up to date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lock automation gated on "did pyproject.toml change vs origin/main?", which is a proxy for the question it actually wanted to ask. A PR that already carries correct lockfiles still tripped the gate and re-ran a full `uv sync` for every affected directory. Gate on `uv lock --check` instead, which is the authoritative staleness test and covers transitive local path dependencies (verified: editing unstract/core/pyproject.toml marks backend/uv.lock stale). Directories whose lockfiles are already correct are now a real no-op. Also swap `uv sync` for `uv lock`. The workflow only ever commits uv.lock, so building a virtualenv and installing every package was wasted work and the largest source of failures in this job. Add a per-branch concurrency group. Two pushes in quick succession started overlapping runs that raced each other's auto-commit, and the loser failed the job with "failed to push some refs" — 3 of the last 30 runs. Auto-commit behaviour is unchanged. --- .github/workflows/uv-lock-automation.yaml | 8 ++ docker/scripts/uv-lock-gen/uv-lock.sh | 93 ++++++----------------- 2 files changed, 30 insertions(+), 71 deletions(-) diff --git a/.github/workflows/uv-lock-automation.yaml b/.github/workflows/uv-lock-automation.yaml index 52c3364386..af4961add2 100644 --- a/.github/workflows/uv-lock-automation.yaml +++ b/.github/workflows/uv-lock-automation.yaml @@ -13,6 +13,14 @@ on: required: false default: "" # Run for all dirs specified in docker/scripts/uv-lock-gen/uv-lock.sh +# Serialize per branch. Pushing twice in quick succession used to start +# overlapping runs that raced each other's auto-commit, and the loser failed the +# whole job with "failed to push some refs". Only the newest run's lockfiles are +# of any interest, so cancel whatever is already in flight for the same branch. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: update_uv_lock: name: Update UV lock in all directories diff --git a/docker/scripts/uv-lock-gen/uv-lock.sh b/docker/scripts/uv-lock-gen/uv-lock.sh index 7180b9fd56..213027e9d1 100755 --- a/docker/scripts/uv-lock-gen/uv-lock.sh +++ b/docker/scripts/uv-lock-gen/uv-lock.sh @@ -1,85 +1,39 @@ #!/bin/bash set -o pipefail -# Extract local path dependencies from [tool.uv.sources] in a pyproject.toml. -# Returns resolved pyproject.toml paths for each local dependency. -get_local_dep_pyprojects() { - local dir="$1" - local file_path="$dir/pyproject.toml" - - grep -A1 'path\s*=' "$file_path" 2>/dev/null \ - | grep -oP 'path\s*=\s*"\K[^"]+' \ - | while read -r rel_path; do - # Resolve relative to the service directory - local dep_pyproject - if [[ "$dir" == "." ]]; then - dep_pyproject="$rel_path/pyproject.toml" - else - dep_pyproject="$dir/$rel_path/pyproject.toml" - fi - # Normalize the path - dep_pyproject=$(realpath --relative-to=. "$dep_pyproject" 2>/dev/null || echo "$dep_pyproject") - if [[ -f "$dep_pyproject" ]]; then - echo "$dep_pyproject" - fi - done -} - -# Check if a directory's own pyproject.toml or any of its local -# path dependencies' pyproject.toml files have changed vs origin/main. -has_dependency_changes() { - local dir="$1" - local file_path="$dir/pyproject.toml" - - # Check direct changes - if ! git diff --quiet origin/main -- "$file_path"; then - echo "[$dir] Changes detected in '$file_path'" - return 0 - fi - - # Check transitive local dependency changes - local dep_pyprojects - dep_pyprojects=$(get_local_dep_pyprojects "$dir") - for dep in $dep_pyprojects; do - if ! git diff --quiet origin/main -- "$dep"; then - echo "[$dir] Changes detected in transitive dependency '$dep'" - return 0 - fi - done - - return 1 -} - # Function to update the lockfile in a directory +# +# `uv lock --check` is the authoritative staleness test: it exits non-zero only +# when uv.lock is missing or no longer agrees with pyproject.toml, transitive +# local path dependencies included. Gating on it means a PR that already carries +# correct lockfiles is a genuine no-op, rather than a re-resolve that rewrites +# the tree and leaves the auto-commit step with nothing useful to push. +# +# `uv lock` (not `uv sync`) is what we want here: the workflow only ever commits +# uv.lock, so building a virtualenv and installing every package is wasted work +# and the single largest source of failures in this job. update_lockfile() { - dir="$1" - file_path="$dir/pyproject.toml" + local dir="$1" + local file_path="$dir/pyproject.toml" if [[ ! -f "$file_path" ]]; then echo "[$dir] No pyproject.toml found in '$dir'" return 0 fi - echo "[$dir] Checking '$file_path' and its dependencies for changes against origin/main..." - if has_dependency_changes "$dir"; then - echo "[$dir] Updating uv.lock for '$dir' ..." + # Run in a subshell so the directory change is scoped to this invocation + ( + cd "$dir" || exit 1 - # Move to the directory if it's not root - if [[ "$dir" != "." ]]; then - cd "$dir" || return 1 + echo "[$dir] Checking whether uv.lock is up to date..." + if uv lock --check >/dev/null 2>&1; then + echo "[$dir] uv.lock is already up to date, nothing to do" + exit 0 fi - # Use uv to generate lock file - echo "[$dir] Updating uv.lock..." - uv sync 2>&1 | sed "s|^|[$dir] |" || return 1 - - # Go back to root if moved to a subdirectory - if [[ "$dir" != "." ]]; then - cd - || return 1 - fi - else - echo "[$dir] No changes detected in '$file_path' or its dependencies" - fi + echo "[$dir] uv.lock is out of date, regenerating..." + uv lock 2>&1 | sed "s|^|[$dir] |" + ) } # https://unix.stackexchange.com/a/124148 @@ -115,9 +69,6 @@ if [ "$#" -gt 0 ]; then directories=("$@") fi -# To compare against main branch -git fetch origin main - # Array to store the job PIDs and directories pids=() dirs=()