1# Monthly bump of the uv pin in `.github/actions/uv_setup/action.yml`.2#3# We pin uv (rather than letting setup-uv resolve latest) because4# `releases.astral.sh` lags GitHub Releases on new uv versions, causing CI5# to flap on fresh-release days. This workflow keeps the pin fresh without6# exposing that race.7#8# Dependabot's `github-actions` ecosystem only updates `uses:` SHA pins, not9# the `UV_VERSION` env value the action passes to `astral-sh/setup-uv`, so we10# open the PR ourselves. Idempotent: if a PR for the target version already11# exists, the workflow exits without creating a duplicate.1213name: "Bump uv pin"1415on:16 schedule:17 - cron: "0 9 1 * *"18 workflow_dispatch:1920permissions:21 contents: read2223concurrency:24 group: bump-uv-pin25 cancel-in-progress: false2627jobs:28 bump:29 if: github.repository_owner == 'langchain-ai'30 name: "Open PR if uv has a newer release"31 runs-on: ubuntu-latest32 permissions:33 contents: write34 pull-requests: write35 steps:36 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v63738 - name: Resolve current and latest uv versions39 id: versions40 env:41 GH_TOKEN: ${{ github.token }}42 run: |43 set -euo pipefail44 action_file=".github/actions/uv_setup/action.yml"45 current=$(grep -oE 'UV_VERSION: "[0-9]+\.[0-9]+\.[0-9]+"' "$action_file" \46 | sed -E 's/UV_VERSION: "([^"]+)"/\1/' | head -n1)47 latest=$(gh api repos/astral-sh/uv/releases/latest --jq .tag_name)48 semver='^[0-9]+\.[0-9]+\.[0-9]+$'49 if [[ ! "$current" =~ $semver ]]; then50 echo "::error::Could not parse current uv pin from $action_file (got '$current')"51 exit 152 fi53 if [[ ! "$latest" =~ $semver ]]; then54 echo "::error::Unexpected uv tag from GitHub API (got '$latest')"55 exit 156 fi57 echo "current=$current" >> "$GITHUB_OUTPUT"58 echo "latest=$latest" >> "$GITHUB_OUTPUT"59 echo "branch=chore/bump-uv-$latest" >> "$GITHUB_OUTPUT"60 echo "Current pin: $current"61 echo "Latest uv: $latest"6263 - name: Log if already up to date64 # The actual skip is implemented by the `if:` guards on every65 # subsequent step; this step only emits a log line so the run66 # history shows why no PR was opened.67 if: steps.versions.outputs.current == steps.versions.outputs.latest68 run: echo "uv pin already at ${{ steps.versions.outputs.latest }}; nothing to do."6970 - name: Skip if PR already open for this version71 id: existing72 if: steps.versions.outputs.current != steps.versions.outputs.latest73 env:74 GH_TOKEN: ${{ github.token }}75 BRANCH: ${{ steps.versions.outputs.branch }}76 run: |77 set -euo pipefail78 count=$(gh pr list --head "$BRANCH" --state open --json number --jq 'length')79 echo "count=$count" >> "$GITHUB_OUTPUT"80 if [ "$count" -gt 0 ]; then81 echo "Open PR already exists for $BRANCH; skipping."82 fi8384 - name: Wait for astral mirror to replicate85 id: mirror86 if: steps.versions.outputs.current != steps.versions.outputs.latest && steps.existing.outputs.count == '0'87 env:88 LATEST: ${{ steps.versions.outputs.latest }}89 run: |90 set -euo pipefail91 # The mirror can lag GitHub Releases. If it hasn't replicated yet,92 # defer the bump rather than landing a pin that races the mirror93 # on every CI run. We probe several arches because partial94 # replication (linux ready, macOS/aarch64 not) would still race95 # CI on other runners.96 assets=(97 "uv-x86_64-unknown-linux-gnu.tar.gz"98 "uv-aarch64-unknown-linux-gnu.tar.gz"99 "uv-x86_64-apple-darwin.tar.gz"100 "uv-aarch64-apple-darwin.tar.gz"101 )102 ready=true103 for asset in "${assets[@]}"; do104 url="https://releases.astral.sh/github/uv/releases/download/${LATEST}/${asset}"105 # `curl -sI` returns nothing on stderr at -s; capture exit code so a106 # permanently broken DNS/TLS path is surfaced instead of collapsing107 # to an opaque "000".108 set +e109 status=$(curl -sIo /dev/null -w '%{http_code}' --max-time 30 "$url" 2>/tmp/curl.err)110 curl_rc=$?111 set -e112 echo "Mirror HEAD $url -> HTTP $status (curl exit=$curl_rc)"113 if [ "$status" != "200" ]; then114 ready=false115 if [ "$curl_rc" -ne 0 ]; then116 echo "::warning::curl failed for $asset (exit=$curl_rc): $(cat /tmp/curl.err 2>/dev/null || true)"117 else118 echo "::warning::astral mirror has not replicated $asset for uv $LATEST yet (HTTP $status)."119 fi120 fi121 done122 if [ "$ready" = "true" ]; then123 echo "ready=true" >> "$GITHUB_OUTPUT"124 else125 echo "ready=false" >> "$GITHUB_OUTPUT"126 echo "::warning::Deferring uv bump to $LATEST until all probed arches are mirrored."127 fi128129 - name: Open bump PR130 if: steps.versions.outputs.current != steps.versions.outputs.latest && steps.existing.outputs.count == '0' && steps.mirror.outputs.ready == 'true'131 env:132 GH_TOKEN: ${{ github.token }}133 CURRENT: ${{ steps.versions.outputs.current }}134 LATEST: ${{ steps.versions.outputs.latest }}135 BRANCH: ${{ steps.versions.outputs.branch }}136 DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}137 run: |138 set -euo pipefail139 action_file=".github/actions/uv_setup/action.yml"140141 # `grep -c` returns 1 on no-match and 2 on read errors. We want142 # "no match" surfaced as the explicit count-of-zero check below;143 # read errors must abort. Capture the exit code separately so144 # `set -e` doesn't swallow either case.145 set +e146 before=$(grep -cE "UV_VERSION: \"${CURRENT}\"" "$action_file")147 before_rc=$?148 set -e149 if [ "$before_rc" -gt 1 ]; then150 echo "::error::grep read error on $action_file (exit=$before_rc)"151 exit 1152 fi153 if [ "$before" -ne 1 ]; then154 echo "::error::Expected exactly 1 'UV_VERSION: \"$CURRENT\"' in $action_file, found $before"155 exit 1156 fi157 sed -i -E "s/UV_VERSION: \"${CURRENT}\"/UV_VERSION: \"${LATEST}\"/" "$action_file"158 set +e159 after=$(grep -cE "UV_VERSION: \"${LATEST}\"" "$action_file")160 after_rc=$?161 set -e162 if [ "$after_rc" -gt 1 ]; then163 echo "::error::grep read error on $action_file (exit=$after_rc)"164 exit 1165 fi166 if [ "$after" -ne 1 ]; then167 echo "::error::Expected exactly 1 'UV_VERSION: \"$LATEST\"' after sed, found $after"168 exit 1169 fi170 if git diff --quiet "$action_file"; then171 echo "No changes after sed; bailing out (current=$CURRENT, latest=$LATEST)."172 exit 1173 fi174175 # Reuse-or-recreate orphan branch from a prior run that pushed176 # but failed before `gh pr create` (no open PR sits on it).177 # The delete can race a concurrent run (manual workflow_dispatch178 # firing while the cron is mid-flight, since concurrency group179 # does not cancel-in-progress); fall through with a warning so a180 # losing race does not kill an otherwise-clean job mid-state.181 if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then182 echo "::warning::Branch $BRANCH exists on origin without an open PR; deleting before recreating."183 if ! git push origin --delete "$BRANCH"; then184 echo "::warning::Delete of $BRANCH failed (concurrent run, or branch already gone); the subsequent push will surface any real conflict."185 fi186 fi187188 git config --local user.name "github-actions[bot]"189 git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"190 git checkout -b "$BRANCH"191 git add "$action_file"192 git commit -m "chore(deps): bump uv to $LATEST"193 git push --set-upstream origin "$BRANCH"194195 body_file="$(mktemp)"196 {197 printf 'Bumps the uv pin in `.github/actions/uv_setup/action.yml` from `%s` to [`%s`](https://github.com/astral-sh/uv/releases/tag/%s).\n\n' "$CURRENT" "$LATEST" "$LATEST"198 printf 'Opened automatically by `bump_uv_pin.yml`. Mirror availability on `releases.astral.sh` was verified before this PR was created, so CI should not race the fallback.\n'199 } > "$body_file"200201 gh pr create \202 --head "$BRANCH" \203 --base "$DEFAULT_BRANCH" \204 --title "chore(deps): bump uv to $LATEST" \205 --body-file "$body_file"
Findings
✓ No findings reported for this file.