1# Validate that a release PR's declared dependencies are actually published on2# PyPI *before* the package itself is released.3#4# WHY: `release(scope): x.y.z` PRs frequently bump intra-monorepo minimum pins5# (e.g. `langchain-core>=1.4.4`). The regular PR test suite deliberately SKIPS6# minimum-version resolution for langchain-core / langchain / langchain-text-splitters7# (see `SKIP_IF_PULL_REQUEST` in `.github/scripts/get_min_versions.py`) because normal8# feature PRs may bump those in lockstep with an as-yet-unpublished sibling release.9#10# For a `release` PR, though, every runtime dependency *should* already be on PyPI —11# that is the convention (release `langchain-core` first, then downstream packages).12# If a pin points at a version that does not exist yet, the published wheel's metadata13# is unresolvable and `pip install <pkg>==x.y.z` breaks for end users. Without this14# workflow, that is only caught at release-trigger time, when `get_min_versions.py`15# resolves the pins against PyPI (its companion change in this PR now exits loudly on16# an unpublished pin instead of emitting `pkg==None`). This workflow adds a second,17# earlier guard: it shifts the same check left onto the release PR, so the author18# finds out before merge rather than when the release job runs.19#20# HOW: resolve each changed package's runtime dependencies against real PyPI with21# `uv pip compile --no-sources`, which ignores the editable `[tool.uv.sources]`22# workspace overrides so intra-monorepo deps resolve from the index exactly as an23# end user's installer would see them. This resolves from package index metadata and24# does not build or run the PR's own project code.2526name: "🚀 Check Release Dependencies"2728on:29 pull_request:30 types: [opened, synchronize, reopened, edited]31 paths:32 - "libs/**/pyproject.toml"3334permissions:35 contents: read3637jobs:38 check-release-deps:39 name: "✅ Verify release dependencies exist on PyPI"40 # Only run for release PRs (`release(scope): x.y.z`). Other PRs may bump41 # intra-monorepo pins ahead of a sibling release on purpose.42 if: startsWith(github.event.pull_request.title, 'release')43 runs-on: ubuntu-latest44 timeout-minutes: 1045 steps:46 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v647 with:48 fetch-depth: 04950 - name: "🐍 Set up Python + uv"51 uses: "./.github/actions/uv_setup"52 with:53 python-version: "3.12"54 enable-cache: "false"5556 - name: "🔍 Resolve runtime dependencies against PyPI"57 shell: bash58 env:59 # Passed via env (not inline interpolation) to keep PR-controlled60 # values out of the shell command string.61 BASE_SHA: ${{ github.event.pull_request.base.sha }}62 HEAD_SHA: ${{ github.event.pull_request.head.sha }}63 run: |64 set -euo pipefail6566 # pyproject.toml manifests changed by this PR.67 mapfile -t changed < <(68 git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'libs/**/pyproject.toml'69 )7071 if [ "${#changed[@]}" -eq 0 ]; then72 # The `paths:` filter should prevent this, so an empty list is73 # surprising — surface it loudly rather than passing silently.74 echo "::notice::No libs/**/pyproject.toml changed in this PR; nothing to validate."75 exit 076 fi7778 failed=079 for manifest in "${changed[@]}"; do80 pkg_dir="$(dirname "$manifest")"81 echo "::group::Resolving ${manifest} against PyPI"82 # --no-sources ignores [tool.uv.sources] editable workspace overrides,83 # so intra-monorepo deps resolve from PyPI like an end-user install.84 # --universal resolves across the full requires-python range, so deps85 # gated behind Python-version markers are validated too.86 if uv pip compile --no-sources --universal "$manifest" > /dev/null; then87 echo "✅ ${pkg_dir}: all runtime dependencies resolve on PyPI"88 else89 echo "❌ ${pkg_dir}: a dependency pin is not satisfiable on PyPI"90 failed=191 fi92 echo "::endgroup::"93 done9495 if [ "$failed" -ne 0 ]; then96 cat >&2 <<'EOF'9798 ┌──────────────────────────────────────────────────────────────────┐99 │ One or more dependency pins could not be resolved from PyPI. │100 │ See the per-package resolver output above for the exact reason. │101 └──────────────────────────────────────────────────────────────────┘102103 Most likely, a `release(scope): x.y.z` PR pinned a dependency to a104 version that is not yet published on PyPI. The released wheel's metadata105 would then be unresolvable, breaking `pip install` for end users. Fix by:106 • Releasing the dependency package first so the pinned version exists107 on PyPI, then re-running this check; or108 • Relaxing the version pin to a published version.109110 If the resolver output above shows a network/index error (rather than111 "No solution found"), this may be a transient PyPI issue — re-run the job.112 EOF113 exit 1114 fi
Findings
✓ No findings reported for this file.