feat: add release workflow#1
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Free Tier Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| - name: Publish to TestPyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| repository-url: https://test.pypi.org |
There was a problem hiding this comment.
TestPyPI repository URL missing /legacy/ path
High Severity
The repository-url for TestPyPI is set to https://test.pypi.org but the correct upload endpoint is https://test.pypi.org/legacy/. Without the /legacy/ path, the publish step will fail because the action won't be able to reach the correct upload API.
| python-version: "3.13" | ||
|
|
||
| - name: Install build tools | ||
| run: pip install build |
There was a problem hiding this comment.
pip unavailable in uv-created virtual environment
High Severity
pip install build will fail because uv venv (used by activate-environment: "true") does not install pip into the virtual environment by default. This is a well-documented intentional behavior of uv. The command needs to be uv pip install build instead, since uv provides its own pip-compatible interface for managing packages in its environments.
There was a problem hiding this comment.
Pull request overview
Adds a GitHub Actions workflow to build Python distribution artifacts on version tag pushes and publish them to TestPyPI first, then to PyPI.
Changes:
- Introduces a tag-triggered release workflow (
v*) for packaging and publishing. - Builds sdist/wheel and passes artifacts between jobs.
- Publishes to TestPyPI, then gates publishing to PyPI on TestPyPI success.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - name: Download all the dists | ||
| uses: actions/download-artifact@v6 |
There was a problem hiding this comment.
Same permissions issue here: restricting the job token to only id-token: write can prevent actions/download-artifact from accessing artifacts. Add the minimal actions: read (and any other required) permissions so the release job doesn't fail mid-pipeline.
| name: infer-check | ||
| path: dist/ | ||
|
|
||
| - name: Publish to TestPyPI |
There was a problem hiding this comment.
In the PyPI publish job, the step name still says "Publish to TestPyPI", which is misleading when debugging failed releases. Rename it to reflect that this job publishes to the real PyPI registry.
| - name: Publish to TestPyPI | |
| - name: Publish to PyPI |
| - name: Publish to TestPyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| repository-url: https://test.pypi.org |
There was a problem hiding this comment.
pypa/gh-action-pypi-publish expects the upload endpoint (legacy) for TestPyPI; https://test.pypi.org will fail to upload. Use the TestPyPI legacy repository URL (and include the trailing slash) per the action docs.
| repository-url: https://test.pypi.org | |
| repository-url: https://test.pypi.org/legacy/ |
| push: | ||
| tags: | ||
| - "v*" | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build distribution | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - name: Set up uv | ||
| uses: astral-sh/setup-uv@v7 | ||
| with: | ||
| activate-environment: "true" | ||
| python-version: "3.13" | ||
|
|
||
| - name: Install build tools | ||
| run: pip install build | ||
|
|
||
| - name: Build sdist and wheel | ||
| run: python -m build | ||
|
|
||
| - name: Store the distribution packages | ||
| uses: actions/upload-artifact@v6 | ||
| with: | ||
| name: infer-check | ||
| path: dist/ | ||
|
|
||
| publish-to-testpypi: | ||
| name: Publish to TestPyPI | ||
| needs: build | ||
| runs-on: ubuntu-latest | ||
| environment: | ||
| name: testpypi | ||
| url: https://test.pypi.org | ||
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - name: Download all the dists | ||
| uses: actions/download-artifact@v6 | ||
| with: | ||
| name: infer-check | ||
| path: dist/ | ||
|
|
||
| - name: Publish to TestPyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| repository-url: https://test.pypi.org | ||
|
|
||
| publish-to-pypi: | ||
| name: Publish to PyPI | ||
| needs: publish-to-testpypi | ||
| runs-on: ubuntu-latest | ||
| environment: | ||
| name: pypi | ||
| url: https://pypi.org | ||
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - name: Download all the dists | ||
| uses: actions/download-artifact@v6 | ||
| with: | ||
| name: infer-check | ||
| path: dist/ | ||
|
|
||
| - name: Publish to TestPyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 |
There was a problem hiding this comment.
Indentation in this workflow uses 4 spaces under on:/jobs: while the existing workflow .github/workflows/coverage.yml uses 2-space indentation. Consider aligning indentation for consistency across workflows (reduces diff noise and makes maintenance easier).
| push: | |
| tags: | |
| - "v*" | |
| jobs: | |
| build: | |
| name: Build distribution | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| activate-environment: "true" | |
| python-version: "3.13" | |
| - name: Install build tools | |
| run: pip install build | |
| - name: Build sdist and wheel | |
| run: python -m build | |
| - name: Store the distribution packages | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: infer-check | |
| path: dist/ | |
| publish-to-testpypi: | |
| name: Publish to TestPyPI | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: testpypi | |
| url: https://test.pypi.org | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Download all the dists | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: infer-check | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: https://test.pypi.org | |
| publish-to-pypi: | |
| name: Publish to PyPI | |
| needs: publish-to-testpypi | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Download all the dists | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: infer-check | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| push: | |
| tags: | |
| - "v*" | |
| jobs: | |
| build: | |
| name: Build distribution | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| activate-environment: "true" | |
| python-version: "3.13" | |
| - name: Install build tools | |
| run: pip install build | |
| - name: Build sdist and wheel | |
| run: python -m build | |
| - name: Store the distribution packages | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: infer-check | |
| path: dist/ | |
| publish-to-testpypi: | |
| name: Publish to TestPyPI | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: testpypi | |
| url: https://test.pypi.org | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Download all the dists | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: infer-check | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: https://test.pypi.org | |
| publish-to-pypi: | |
| name: Publish to PyPI | |
| needs: publish-to-testpypi | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Download all the dists | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: infer-check | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 |
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - name: Download all the dists | ||
| uses: actions/download-artifact@v6 | ||
| with: |
There was a problem hiding this comment.
These publish jobs set permissions to only id-token: write, which removes the default token permissions. actions/download-artifact typically needs actions: read (and sometimes contents: read) on GITHUB_TOKEN to fetch artifacts; without it the download step can fail. Add the minimal required permissions alongside id-token: write.
| run: pip install build | ||
|
|
||
| - name: Build sdist and wheel | ||
| run: python -m build |
There was a problem hiding this comment.
This workflow sets up uv but then uses pip install build and python -m build. Elsewhere in the repo (e.g., Makefile and .github/workflows/coverage.yml) commands are consistently run via uv pip / uv run; using uv here too keeps the environment consistent and avoids accidentally invoking the system pip/python.
| run: pip install build | |
| - name: Build sdist and wheel | |
| run: python -m build | |
| run: uv pip install build | |
| - name: Build sdist and wheel | |
| run: uv run -m build |


Note
Medium Risk
Adds an automated release pipeline that publishes artifacts to TestPyPI and then PyPI on
v*tags, which can impact the package supply chain if misconfigured. Risk is limited to CI/config changes but affects production distribution.Overview
Introduces a new GitHub Actions workflow (
.github/workflows/release.yml) that triggers onv*tags to build sdist/wheels withuv(Python 3.13), upload them as an artifact, and then publish sequentially to TestPyPI and PyPI using OIDC (pypa/gh-action-pypi-publish).Written by Cursor Bugbot for commit 26ca62b. This will update automatically on new commits. Configure here.