fix(ci): publish to PyPI on the tag push, not the derivative release event#2113
Conversation
…event PyPI sat five releases behind (0.101.0) while Docker and the GitHub Releases were current. Nobody noticed, because nothing alerts on a trigger that simply doesn't happen — you only find it by going and looking at pypi.org. `publish.yml` triggered on `release: published`, an event release.yml PRODUCES. Its own header admitted "the release trigger can be flaky". A derivative event is a worse contract than the thing it derives from: the tag push is what actually occurs, and release.yml has been running on it reliably the whole time. - Trigger is now `push: tags: ['v*.*.*']` — same event as release.yml, the two independent and in parallel. publish.yml never needed the GitHub Release to exist (it only builds and uploads a wheel), so there was no ordering reason for the indirection. - The version-matches-tag guard keyed on the EVENT NAME (`= "release"`), so a workflow_dispatch skipped validation entirely — the manual fallback, i.e. the path you take precisely when the trigger has misfired, was also the unguarded one. It now gates on `GITHUB_REF_TYPE = tag`, so any tag ref is validated (tag push and dispatch-against-a-tag alike) while a dispatch from a branch stays an unvalidated test build, as intended. - `skip-existing: true` — PyPI refuses re-uploads, so a re-run or a manual dispatch after a successful automatic run would fail the job and read as a broken release. Publishing stays exactly-once; only the spurious error goes. - `concurrency` per ref, queueing rather than cancelling: a half-finished upload is worse than a duplicate, and the duplicate is now a no-op anyway. Deliberately NOT done: invoking publish.yml as a reusable workflow from release.yml. PyPI Trusted Publishing matches on the workflow FILENAME, so the OIDC claim would become release.yml and the upload would be rejected. Noted in the header so the next person doesn't try it. docs/guides/releasing.md never mentioned PyPI at all — part of why the lag went unseen. It now shows the tag fanning out to both workflows, has the desktop dispatch as an explicit step (it's not tag-triggered, and until it runs the release stays non-Latest so the in-app updater keeps offering the previous version), and ends with a three-channel post-release verification snippet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 14 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
QA panel review — PASS
code-review-structural · head 86534581d692 · formal
[review-synthesizer completed: workflow code-review-structural:report]
The PR is low-risk as no defects were identified by the review panel or the structural analysis pass. There are no items to fix and no points of disagreement. Verification confirmed the absence of findings.
[]…rtifacts, fleet inputs/secrets (#2130) Bump 0.106.0 → 0.107.0 and roll the changelog for the release. Highlights: - Versioned file artifacts + save_file_artifact (ADR 0092 D2, #2126) - Document-generation stack baked into the desktop bundle (ADR 0092 D1, #2123) - Fleet agents accept operator inputs + secrets at create time (#2121/#2122/#2125/#2127) - Deterministic persona-drift detection (#2116) - Chat tab context menu Close Others/Left/Right (#2112) - Fixes: archetype-catalog bundling (#2115), PyPI publishes on tag push (#2113) Backfilled [Unreleased] entries for the feature PRs that lacked them, rolled to [0.107.0], scaffolded + polished the marketing changelog. uv.lock: project version line only (surgical — avoids uv 0.11.13-vs-pinned-0.11.29 marker drift). Claude-Session: https://claude.ai/code/session_01EVmWoy2TdXdMshNn5WBR2Z Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PyPI sat five releases behind (0.101.0) while Docker and the GitHub Releases were current. Nobody noticed, because nothing alerts on a trigger that doesn't happen — you only find it by going and looking at pypi.org. I hit it cutting v0.106.0.
Root cause
publish.ymltriggered onrelease: published— an event thatrelease.ymlproduces. Its own header already admitted "thereleasetrigger can be flaky."A derivative event is a worse contract than the thing it derives from. The tag push is what actually happens, and
release.ymlhas been running on it reliably the whole time.Changes
release: publishedpush: tags: ['v*.*.*']event == releaseGITHUB_REF_TYPE)skip-existing→ no-oppublish.ymlnever needed the GitHub Release to exist — it only builds and uploads a wheel — so there was no ordering reason for the indirection. The two workflows now run independently off the same tag.The guard fix matters on its own. It keyed on the event name, so a
workflow_dispatchskipped version validation entirely — meaning the manual fallback, the path you take precisely when the trigger has misfired, was also the unvalidated one. It now gates on the ref type: tag push and dispatch-against-a-tag are both checked; a dispatch from a branch stays an unvalidated test build, as intended.Deliberately not done
Invoking
publish.ymlas a reusable workflow fromrelease.yml. PyPI Trusted Publishing matches on the workflow filename — the OIDC claim would becomerelease.ymland the upload would be rejected. Noted in the workflow header so the next person doesn't try it.Docs
docs/guides/releasing.mdnever mentioned PyPI at all — part of why the lag went unseen. It now:Testing
All workflow YAML parses; asserted the resulting config programmatically — trigger set, tag filter, concurrency,
skip-existing, guard now onGITHUB_REF_TYPE, andreleaseno longer a trigger.ruffclean, changelog check passes.Can't fully exercise until the next tag — but v0.106.0 is already published to PyPI, so the first run under this change will exercise the
skip-existingpath rather than a fresh upload.🤖 Generated with Claude Code