Uh oh!
There was an error while loading. Please reload this page.
Harden PyPI install verification against stale cache - #290
Conversation
The verify loop assumed a new release appears in PyPI's simple index within its 10-minute budget. Both socketsecurity 2.5.9 and socketdev 3.4.2 (2026-08-05) took longer than that: the upload succeeded and the JSON API showed the release immediately, but the CDN-cached simple index kept serving a stale version list past the loop's last attempt, failing the release and skipping the Docker publish. Extend the retry window to 30 minutes, add --no-cache-dir so each attempt refetches the index rather than revalidating pip's locally cached stale copy, and log when the JSON API already has the version so index staleness is distinguishable from a failed publish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
Use python -m pip with an explicit production Simple-index URL, quote workflow outputs, and skip the sleep after the final attempt, matching the socket-sdk-python release workflow so the verify step is identical in both repos. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
David Larsen (dc-larsen)
left a comment
There was a problem hiding this comment.
Approving — reproduced the root cause locally and confirmed the fix.
Ran pip 26.2.1 against a local PEP 503 index serving Cache-Control: max-age=600 (what pypi.org/simple actually returns). With a stale index cached, pip served the stale version list from its own HTTP cache and never re-requested the index on retry; adding --no-cache-dir picked up the new version immediately. So the old 30 × 20s budget (attempts at t=0..580s) sat entirely inside the 600s cache window — once attempt 1 missed, no amount of waiting could recover. --no-cache-dir is the change that matters here; the longer window covers genuine CDN-side staleness on top of it.
Also verified both loop paths (success writes success=true/exit 0; failure writes success=false/exit 1 with no trailing sleep) and the verify step is shellcheck- and actionlint-clean.
Uh oh!
There was an error while loading. Please reload this page.
Summary
The release workflow's "Verify package is installable" step retries
pip install socketsecurity==$VERSIONfor 10 minutes (30 × 20s). That budget assumed PyPI's simple index reflects a successful upload within seconds, which held until 2026-08-05, when two releases in one day exceeded it:socketsecurity2.5.9 — uploaded 16:40:20 UTC; all 30 verify attempts (16:40:35–16:50:46) got a version list ending at 2.5.8. The release run failed and the Docker publish was skipped, sosocketdev/cli:2.5.9was never pushed.socketdev3.4.2 — uploaded 23:29:39 UTC; same failure 23:30–23:39 in that repo's identical verify step. Meanwhile other CI runners installed 3.4.2 from PyPI successfully during the same minutes, so the staleness was specific to individual CDN cache nodes.In both cases the JSON API (
/pypi/<pkg>/<version>/json) showed the release immediately; only the CDN-cached simple index (/simple/<pkg>/, which pip actually uses) was stale. PyPI's status page reported no incident. This failure class has recurred on PyPI's side for years (see references below).Changes
--no-cache-dirso each attempt fully refetches the index. The simple index is served withCache-Control: max-age=600, so without this, pip revalidates its locally cached copy by ETag and a stale CDN node can keep answering 304 for the whole loop.python -m pipwith an explicit--index-url https://pypi.org/simple/, and quote workflow output redirects.The verify step is now line-for-line consistent with the same hardening in SocketDev/socket-sdk-python#102.
Notes
pypi_existscheck makes the rerun idempotent.References
Prior reports of the same PyPI simple-index staleness class: