From 07ae95f9b779c1e9ae60741d2d55c20ed236a994 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:36:46 -0400 Subject: [PATCH 1/3] fix(ci): refresh PyPI install verification --- .github/workflows/release.yml | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index daae060..93b44a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: REF_NAME: ${{ github.ref_name }} run: | RAW_VERSION=$(hatch version) - echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV + echo "VERSION=$RAW_VERSION" >> "$GITHUB_ENV" if [ "v$RAW_VERSION" != "$REF_NAME" ]; then echo "Error: Git tag ($REF_NAME) does not match hatch version (v$RAW_VERSION)" exit 1 @@ -38,12 +38,12 @@ jobs: env: VERSION: ${{ env.VERSION }} run: | - if curl -s -f https://pypi.org/pypi/socketdev/$VERSION/json > /dev/null; then + if curl -s -f "https://pypi.org/pypi/socketdev/$VERSION/json" > /dev/null; then echo "Version ${VERSION} already exists on PyPI" - echo "pypi_exists=true" >> $GITHUB_OUTPUT + echo "pypi_exists=true" >> "$GITHUB_OUTPUT" else echo "Version ${VERSION} not found on PyPI - proceeding with PyPI deployment" - echo "pypi_exists=false" >> $GITHUB_OUTPUT + echo "pypi_exists=false" >> "$GITHUB_OUTPUT" fi - name: Build package @@ -60,15 +60,24 @@ jobs: env: VERSION: ${{ env.VERSION }} run: | - for i in {1..30}; do - if pip install socketdev==${VERSION}; then + # The first lookup can race PyPI's Simple-index propagation. pip caches HTTP + # responses by default, so without --no-cache-dir every retry can reuse that + # initial stale response instead of checking whether the release has appeared. + MAX_ATTEMPTS=40 + for i in $(seq 1 "$MAX_ATTEMPTS"); do + if python -m pip install \ + --no-cache-dir \ + --index-url https://pypi.org/simple/ \ + "socketdev==${VERSION}"; then echo "Package ${VERSION} is now available and installable on PyPI" - pip uninstall -y socketdev - echo "success=true" >> $GITHUB_OUTPUT + python -m pip uninstall -y socketdev + echo "success=true" >> "$GITHUB_OUTPUT" exit 0 fi - echo "Attempt $i: Package not yet installable, waiting 20s... (${i}/30)" - sleep 20 + if [ "$i" -lt "$MAX_ATTEMPTS" ]; then + echo "Attempt $i: Package not yet installable, waiting 20s... (${i}/${MAX_ATTEMPTS})" + sleep 20 + fi done - echo "success=false" >> $GITHUB_OUTPUT + echo "success=false" >> "$GITHUB_OUTPUT" exit 1 From 24b73fe8df90e9bbb9e34f412bf0ca7869140780 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:13:49 -0400 Subject: [PATCH 2/3] fix(ci): extend PyPI verify budget to 30 minutes and log index staleness Match the socket-python-cli release workflow hardening (PR #290 there): the 2026-08-05 propagation delay exceeded 10 minutes from the release runner's vantage point, so extend the retry budget to 30 minutes, and log when the JSON API already has the version but the Simple index does not, making CDN propagation delay distinguishable from a failed publish in the logs. Co-Authored-By: Claude Fable 5 Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/release.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93b44a7..96c5180 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,10 +60,13 @@ jobs: env: VERSION: ${{ env.VERSION }} run: | - # The first lookup can race PyPI's Simple-index propagation. pip caches HTTP - # responses by default, so without --no-cache-dir every retry can reuse that - # initial stale response instead of checking whether the release has appeared. - MAX_ATTEMPTS=40 + # The first lookup can race PyPI's Simple-index propagation, and a delayed + # CDN purge can leave the index stale for 10+ minutes after a successful + # upload (socketdev 3.4.2 and socketsecurity 2.5.9 both hit this on + # 2026-08-05). pip caches HTTP responses by default, so without + # --no-cache-dir every retry can reuse that initial stale response instead + # of checking whether the release has appeared. Budget: 30 minutes. + MAX_ATTEMPTS=60 for i in $(seq 1 "$MAX_ATTEMPTS"); do if python -m pip install \ --no-cache-dir \ @@ -74,9 +77,12 @@ jobs: echo "success=true" >> "$GITHUB_OUTPUT" exit 0 fi + if curl -s -f "https://pypi.org/pypi/socketdev/${VERSION}/json" > /dev/null; then + echo "Release ${VERSION} exists on PyPI (JSON API) but is not in the Simple index yet - CDN propagation delay" + fi if [ "$i" -lt "$MAX_ATTEMPTS" ]; then - echo "Attempt $i: Package not yet installable, waiting 20s... (${i}/${MAX_ATTEMPTS})" - sleep 20 + echo "Attempt $i: Package not yet installable, waiting 30s... (${i}/${MAX_ATTEMPTS})" + sleep 30 fi done echo "success=false" >> "$GITHUB_OUTPUT" From a38ab2597f8f9aaf7ab64b9c7d046138cdf6f34e Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:38:17 -0400 Subject: [PATCH 3/3] Trim release-specific details from verify step comment Co-Authored-By: Claude Fable 5 Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/release.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96c5180..86ae79c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,11 +61,10 @@ jobs: VERSION: ${{ env.VERSION }} run: | # The first lookup can race PyPI's Simple-index propagation, and a delayed - # CDN purge can leave the index stale for 10+ minutes after a successful - # upload (socketdev 3.4.2 and socketsecurity 2.5.9 both hit this on - # 2026-08-05). pip caches HTTP responses by default, so without - # --no-cache-dir every retry can reuse that initial stale response instead - # of checking whether the release has appeared. Budget: 30 minutes. + # CDN purge can leave the index stale well after a successful upload. + # pip caches HTTP responses by default, so without --no-cache-dir every + # retry can reuse that initial stale response instead of checking whether + # the release has appeared. Budget: 30 minutes. MAX_ATTEMPTS=60 for i in $(seq 1 "$MAX_ATTEMPTS"); do if python -m pip install \