Skip to content

fix(ci): publish to PyPI on the tag push, not the derivative release event#2113

Merged
mabry1985 merged 1 commit into
mainfrom
fix/pypi-publish-trigger
Jul 21, 2026
Merged

fix(ci): publish to PyPI on the tag push, not the derivative release event#2113
mabry1985 merged 1 commit into
mainfrom
fix/pypi-publish-trigger

Conversation

@mabry1985

Copy link
Copy Markdown
Member

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.yml triggered on release: published — an event that release.yml produces. Its own header already 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 happens, and release.yml has been running on it reliably the whole time.

Changes

Before After
Trigger release: published push: tags: ['v*.*.*']
Version-vs-tag guard only when event == release any tag ref (GITHUB_REF_TYPE)
Duplicate upload job fails red skip-existing → no-op
Concurrency none one per ref, queued not cancelled

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 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_dispatch skipped 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.yml as a reusable workflow from release.yml. PyPI Trusted Publishing matches on the workflow filename — the OIDC claim would become release.yml and the upload would be rejected. Noted in the workflow header so the next person doesn't try it.

Docs

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 tag-triggered workflows, with a note on why the indirection was removed
  • makes the desktop dispatch an explicit step: it isn't tag-triggered (paid CI), and until it runs the release stays non-Latest, so the in-app updater keeps offering the previous version
  • ends with a three-channel post-release verification snippet (GitHub Release / Docker / PyPI)

Testing

All workflow YAML parses; asserted the resulting config programmatically — trigger set, tag filter, concurrency, skip-existing, guard now on GITHUB_REF_TYPE, and release no longer a trigger. ruff clean, 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-existing path rather than a fresh upload.

🤖 Generated with Claude Code

…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>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@mabry1985, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d4fd33a6-ea95-424f-a7e0-d3856af3455d

📥 Commits

Reviewing files that changed from the base of the PR and between 4a77725 and 8653458.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !*.md
📒 Files selected for processing (2)
  • .github/workflows/publish.yml
  • docs/guides/releasing.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pypi-publish-trigger

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@protoreview protoreview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

[]

@mabry1985
mabry1985 merged commit 0968a42 into main Jul 21, 2026
10 checks passed
@mabry1985
mabry1985 deleted the fix/pypi-publish-trigger branch July 21, 2026 19:58
mabry1985 added a commit that referenced this pull request Jul 22, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant