ci(release): create GitHub Release after successful PyPI publish (VER-001) - #48
ci(release): create GitHub Release after successful PyPI publish (VER-001)#48yakimoto wants to merge 1 commit into
Conversation
…-001) Adds a `release` job to release.yml, gated on `needs: publish` with `if: needs.publish.result == success` (publish itself only runs on a real v* tag push and includes post-publish PyPI verification) so it never fires on a failed publish or a PR dry-run. Scoped to contents: write on just that job (workflow-level permissions stay contents: read). Idempotent: re-uploads dist/ with --clobber if the release already exists for the tag.
|
ⓘ Qodo reviews are paused because your workspace is out of credits. Ask your workspace admin to add credits to resume reviews. Manage billing |
🤖 CodeAnt AI — Review Status
|
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
There was a problem hiding this comment.
Sorry @yakimoto, this account has used its review budget of 2,500,000 diff characters for the last 7 days.
You can request another review in 14 hours and 48 minutes by commenting @sourcery-ai review.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_30939d01-6471-4c5a-9457-d86aa16c46ab) |
Reviewer's GuideThe workflow now automatically creates or repairs a GitHub Release with the built distribution artifacts after a tag-triggered PyPI publish is confirmed live, improving version/release consistency while preserving scoped permissions and adding post-upload validation. Sequence diagram for verified PyPI publishing and GitHub Release creationsequenceDiagram
participant Publish as publish job
participant PyPI as PyPI
participant Release as release job
participant GitHub as GitHub Releases
Publish->>PyPI: twine upload
Publish->>PyPI: Post-publish verification
PyPI-->>Publish: Artifact live and installable
Publish-->>Release: needs.publish success
Release->>GitHub: download-artifact dist
alt Release exists
Release->>GitHub: gh release upload --clobber
else Release does not exist
Release->>GitHub: gh release create --generate-notes
end
Release->>GitHub: gh release view --json assets
GitHub-->>Release: Release contains every dist asset
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (1)
Included review availability: 0 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour. 📜 Recent review details⏰ Context from checks skipped due to timeout. (2)
🔇 Additional comments (1)
📝 SummarySummary by CodeRabbit
WalkthroughThe release workflow now creates or updates a GitHub Release after successful PyPI publishing. It downloads distribution artifacts, uploads them with idempotent behavior, and verifies that every artifact is attached. ChangesRelease publication
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to Successful tagged PyPI publications will now produce or update a matching GitHub Release with the built distribution assets. Artifact presence and completeness checks limit the risk of publishing incomplete releases. Sequence Diagram(s)sequenceDiagram
participant PyPI
participant GitHubActions
participant GitHubRelease
PyPI-->>GitHubActions: successful publication
GitHubActions->>GitHubActions: download dist artifact
GitHubActions->>GitHubRelease: create or update release
GitHubActions->>GitHubRelease: upload distribution assets
GitHubRelease-->>GitHubActions: release assets
GitHubActions->>GitHubActions: verify every distribution asset
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
|
Note Automatic reviews are paused because your team has used its included automatic processing for this billing period (headroom scales with your seat count). You can still comment "Gitar review" to run one anytime, and automatic reviews resume on their own by October 1. Add seats for more headroom. Code Review ✅ ApprovedAdds a OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — The workflow now automatically creates or mutates public GitHub Releases and uploads distribution artifacts after successful PyPI publication, using a new contents:write permission. This expands the production release process beyond routine CI configuration and introduces a durable external side effect. Not approved because:
Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more. |
| - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | ||
| with: | ||
| name: dist | ||
| path: dist |
There was a problem hiding this comment.
Suggestion: The release downloads artifacts from the earlier build, while PyPI receives a separate rebuild, so non-reproducible metadata can make release assets differ from published artifacts. [api mismatch]
Assessment: 🟠 Major · 🔁 Occurrence: Sometimes
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** .github/workflows/release.yml
**Line:** 156:159
**Comment:**
*Api Mismatch: The release downloads artifacts from the earlier build, while PyPI receives a separate rebuild, so non-reproducible metadata can make release assets differ from published artifacts.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| echo "::error::no files under dist/ to attach to the release" | ||
| exit 1 | ||
| fi | ||
| if gh release view "$TAG_NAME" >/dev/null 2>&1; then |
There was a problem hiding this comment.
Suggestion: Any gh release view failure is treated as absence, so authentication, repository, or transient API errors trigger misleading creation attempts and hide the original failure. [logic error]
Assessment: 🟠 Major · 🔁 Occurrence: Rarely
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** .github/workflows/release.yml
**Line:** 176:176
**Comment:**
*Logic Error: Any `gh release view` failure is treated as absence, so authentication, repository, or transient API errors trigger misleading creation attempts and hide the original failure.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fixThere was a problem hiding this comment.
3 issues found across 1 file
Confidence score: 2/5
- In
.github/workflows/release.yml, treating everygh release viewfailure as “not found” can mask authentication, repository, or transient API failures and trigger a misleading release creation—distinguish not-found responses before entering the create branch. - In
.github/workflows/release.yml, rebuilding instead of publishing the exactdist/artifact used by the PyPI job can produce different metadata or bytes, leaving the GitHub Release out of sync—reuse the existing artifact or make the release consume the PyPI job’s artifact. - In
.github/workflows/release.yml, deleting the tag beforegh release createcan cause the release to be created from the default branch while attaching artifacts built from the original tag—add--verify-tagso the job fails rather than publishing a mismatched release.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/release.yml">
<violation number="1" location=".github/workflows/release.yml:158">
P1: Publish the exact `dist/` artifact used here, or make the release consume the artifact produced by the PyPI publish job. A separate rebuild can produce different metadata or bytes, leaving the GitHub Release out of sync with the package verified on PyPI.</violation>
<violation number="2" location=".github/workflows/release.yml:176">
P1: Distinguish a not-found response from other `gh release view` failures before entering the create branch. Otherwise authentication, repository, or transient API errors are masked and can produce a misleading create attempt.</violation>
<violation number="3" location=".github/workflows/release.yml:181">
P2: If the tag is deleted before this command runs, `gh release create` creates it from the default branch and attaches artifacts built from the original tag. Add `--verify-tag` so the job fails instead of publishing a release for a different commit.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Git as Git Tag Push
participant WF as Workflow Dispatch
participant Build as build Job
participant Pub as publish Job
participant PyPI as PyPI Registry
participant Rel as release Job
participant GH as GitHub Release
participant Art as Artifact Store
Note over Git,GH: VER-001: GitHub Release after verified PyPI publish
Git->>WF: push tag (refs/tags/v*)
WF->>Build: checkout + build
Build->>Art: upload dist/ artifact
Art-->>Build: artifact stored
alt tag matches refs/tags/v*
Build->>Pub: need success
Pub->>PyPI: twine upload / Trusted Publishing
PyPI-->>Pub: published
Pub->>PyPI: pip index versions (verify)
PyPI-->>Pub: package visible
Pub->>PyPI: install fresh wheel
PyPI-->>Pub: install OK
Pub->>Pub: assert wave_sdk.__version__
alt publish success
Pub-->>Rel: trigger (needs.publish.result == 'success')
Rel->>Art: download dist/ (pinned SHA)
Art-->>Rel: dist/ files
Rel->>GH: gh release view TAG_NAME
alt release exists
GH-->>Rel: found
Rel->>GH: upload --clobber (idempotent)
else no release
GH-->>Rel: not found
Rel->>GH: create --generate-notes
end
Rel->>GH: verify all assets attached
GH-->>Rel: asset list
alt all files present
Rel-->>WF: VER-001 satisfied
else file missing
Rel-->>WF: fail with error
end
end
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if gh release view "$TAG_NAME" >/dev/null 2>&1; then | ||
| echo "release $TAG_NAME already exists — uploading dist/ (idempotent path, --clobber)" | ||
| gh release upload "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --clobber | ||
| else | ||
| echo "release $TAG_NAME does not exist — creating with generated notes" | ||
| gh release create "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --title "$TAG_NAME" --generate-notes | ||
| fi |
There was a problem hiding this comment.
P1: Distinguish a not-found response from other gh release view failures before entering the create branch. Otherwise authentication, repository, or transient API errors are masked and can produce a misleading create attempt.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yml, line 176:
<comment>Distinguish a not-found response from other `gh release view` failures before entering the create branch. Otherwise authentication, repository, or transient API errors are masked and can produce a misleading create attempt.</comment>
<file context>
@@ -134,3 +134,62 @@ jobs:
+ echo "::error::no files under dist/ to attach to the release"
+ exit 1
+ fi
+ if gh release view "$TAG_NAME" >/dev/null 2>&1; then
+ echo "release $TAG_NAME already exists — uploading dist/ (idempotent path, --clobber)"
+ gh release upload "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --clobber
</file context>
| if gh release view "$TAG_NAME" >/dev/null 2>&1; then | |
| echo "release $TAG_NAME already exists — uploading dist/ (idempotent path, --clobber)" | |
| gh release upload "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --clobber | |
| else | |
| echo "release $TAG_NAME does not exist — creating with generated notes" | |
| gh release create "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --title "$TAG_NAME" --generate-notes | |
| fi | |
| release_view_error="$(mktemp)" | |
| if gh release view "$TAG_NAME" >/dev/null 2>"$release_view_error"; then | |
| rm -f "$release_view_error" | |
| echo "release $TAG_NAME already exists — uploading dist/ (idempotent path, --clobber)" | |
| gh release upload "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --clobber | |
| else | |
| release_view_status=$? | |
| if ! grep -Eqi 'not found|404' "$release_view_error"; then | |
| cat "$release_view_error" >&2 | |
| rm -f "$release_view_error" | |
| exit "$release_view_status" | |
| fi | |
| rm -f "$release_view_error" | |
| echo "release $TAG_NAME does not exist — creating with generated notes" | |
| gh release create "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --title "$TAG_NAME" --generate-notes | |
| fi |
| steps: | ||
| - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | ||
| with: | ||
| name: dist |
There was a problem hiding this comment.
P1: Publish the exact dist/ artifact used here, or make the release consume the artifact produced by the PyPI publish job. A separate rebuild can produce different metadata or bytes, leaving the GitHub Release out of sync with the package verified on PyPI.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yml, line 158:
<comment>Publish the exact `dist/` artifact used here, or make the release consume the artifact produced by the PyPI publish job. A separate rebuild can produce different metadata or bytes, leaving the GitHub Release out of sync with the package verified on PyPI.</comment>
<file context>
@@ -134,3 +134,62 @@ jobs:
+ steps:
+ - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: dist
+ path: dist
+
</file context>
| gh release upload "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --clobber | ||
| else | ||
| echo "release $TAG_NAME does not exist — creating with generated notes" | ||
| gh release create "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --title "$TAG_NAME" --generate-notes |
There was a problem hiding this comment.
P2: If the tag is deleted before this command runs, gh release create creates it from the default branch and attaches artifacts built from the original tag. Add --verify-tag so the job fails instead of publishing a release for a different commit.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yml, line 181:
<comment>If the tag is deleted before this command runs, `gh release create` creates it from the default branch and attaches artifacts built from the original tag. Add `--verify-tag` so the job fails instead of publishing a release for a different commit.</comment>
<file context>
@@ -134,3 +134,62 @@ jobs:
+ gh release upload "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --clobber
+ else
+ echo "release $TAG_NAME does not exist — creating with generated notes"
+ gh release create "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --title "$TAG_NAME" --generate-notes
+ fi
+ echo "verifying the release exists and carries every dist/ asset"
</file context>
| gh release create "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --title "$TAG_NAME" --generate-notes | |
| gh release create "$TAG_NAME" "${ASSETS_TO_UPLOAD[@]}" --title "$TAG_NAME" --generate-notes --verify-tag |
User description
Why
GA gate criterion VER-001 ("Version and release truth agree from source through
deployment") is a must-pass blocker, currently
fail. The measured finding(
governance/ga-gate/evidence/VER-001-release-ledger-2026-09-05.mdinwave-av/claude-workstation) is that norelease.ymlin any of the four publicrelease-publishing repos ever creates a GitHub Release — this repo's "latest" Release
is
v1.0.0(2026-04-05) whilemain/pyproject.tomlis already at2.1.0.What changed
Adds a
releasejob to.github/workflows/release.yml, after the existingpublishjob:
needs: publishwithif: needs.publish.result == 'success'.publishitself only runs
if: startsWith(github.ref, 'refs/tags/v')(never on apull_requestdry-run) and already includes its own "Post-publish verification"step (waits for
pip index versions, installs the fresh wheel from PyPI, assertswave_sdk.__version__), so this job only ever fires once the artifact is confirmedlive.
contents: writescoped to only this job. Workflow-levelpermissions:is unchanged (contents: read).env: TAG_NAME: ${{ github.ref_name }}, read as a shell variable; no${{ }}appears inside anyrun:body in the new job.
dist/artifactsre-uploaded with
--clobberinstead of failing.distartifact thebuildjob already uploads(
actions/upload-artifact), viaactions/download-artifact@018cc2cf…pinned by fullcommit SHA (verified against
gh api repos/actions/download-artifact/git/refs/tags/v6.0.0—
018cc2cf5baa6db3ef3c5f8a56943fffe632ef53), and attaches every file underdist/to the Release.
Verification performed
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))"—parses clean, 3 jobs (
build, publish, release).actionlint .github/workflows/release.yml— exit 0, no findings.${{ }}inside arun:body in the new job (only insideenv:).permissions:: workflow stayscontents: read;publishkeepsid-token: write+contents: read; onlyreleaseaddscontents: write.download-artifactSHA pin against the GitHub API directly, rather thantrusting a remembered value — an earlier draft of this pin was wrong.
What this PR does NOT do
This PR does not create any GitHub Release, and there is no back-fill candidate
for this repo today — and the gap here is worse than a missing Release. This repo's
own package (
wave-sdkon PyPI) is stuck at2.0.0(
curl https://pypi.org/pypi/wave-sdk/json→info.version = "2.0.0"), even thoughmain'spyproject.tomlis at2.1.0and av2.1.0tag already exists. Per the GAevidence doc, this repo's own
release.ymlrun onv2.1.0failed withinvalid-publisher— PyPI's registered Trusted Publisher forwave-sdkdoes notresolve to this repo. Separately, the different package name
wave-av-sdk(currently3.0.0on PyPI) carries provenance pointing atwave-av/sdks, not this repo. Neitherv2.1.0(unpublished here) nor any other tag has an artifact on the registry this repoactually publishes to, so there is nothing to back-fill: (b) does not apply until the
Trusted Publisher / package-ownership question (item (d) in the evidence doc's remediation
list) is resolved by Jake.
Part of the VER-001 GA burn-down lane (
wave-av/claude-workstationgovernance/ga-gate/spec/WAVE-GA-burndown-v1.0.0.json).Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Note
Cursor Bugbot is generating a summary for commit 5c8efc0. Configure here.
Summary by Sourcery
Create a verified, artifact-backed GitHub Release after each successful PyPI publication.
New Features:
Enhancements:
CI:
Tests:
CodeAnt-AI Description
Create verified GitHub Releases after successful package publishing
What Changed
Impact
✅ GitHub Releases stay aligned with verified PyPI packages✅ Downloadable distribution files on every release✅ Safe retries for interrupted or repeated releases💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.