Let me explore the repository to understand the PyPI publishing functionality.
Now let me check the events configuration to understand the push event requirement:
Now let me check the test for push handler to understand failure scenarios:
Now let me check how events are configured to make sure push is needed:
Now I have a comprehensive understanding. Let me also check how the .github-webhook-server.yaml is loaded for per-repo config:
Now I have all the information I need. Let me write the documentation page.
Publishing Packages to PyPI
Automatically publish your Python package to PyPI every time you push a Git tag, with built-in failure alerts and optional Slack notifications.
Prerequisites
- A PyPI API token scoped to your project
- Your repository configured in the webhook server (see Configuring Repositories)
- The
pushevent enabled in your repository's events list - Your project must be buildable with
uv build --sdist
Quick Example
Add the pypi section to your repository configuration:
# In config.yaml, under your repository
repositories:
my-org/my-python-package:
pypi:
token: pypi-AgEIcH...your-token-here
events:
- push
- pull_request
- pull_request_review
- issue_comment
- check_run
- status
Now push a tag:
git tag v1.2.0
git push origin v1.2.0
The webhook server builds your package and uploads it to PyPI automatically.
Step-by-Step Setup
1. Generate a PyPI API Token
- Log in to pypi.org and go to Account Settings → API Tokens.
- Create a token scoped to your specific project (recommended) or account-wide.
- Copy the token — it starts with
pypi-.
2. Add the Token to Your Configuration
You can configure PyPI publishing in either location:
| Config file | Scope |
|---|---|
config.yaml |
Centralized — managed by the server admin |
.github-webhook-server.yaml (in repo root) |
Per-repository — managed by repo maintainers |
Option A: Central config (config.yaml)
repositories:
my-org/my-python-package:
pypi:
token: pypi-AgEIcH...your-token-here
Option B: Per-repo config (.github-webhook-server.yaml)
pypi:
token: pypi-AgEIcH...your-token-here
Tip: Per-repo config in
.github-webhook-server.yamloverrides the centralconfig.yamlvalues, so teams can manage their own PyPI tokens independently.Warning: PyPI tokens are secrets. Use environment variable substitution or a secret manager to avoid committing tokens in plain text. The schema marks the token field with
format: password, and the server redacts it from logs whenmask-sensitive-datais enabled (the default).
3. Include the push Event
Make sure push is in your repository's event list. If you omit the events key entirely, all events are listened to by default. If you specify events explicitly, include push:
events:
- push
- pull_request
- pull_request_review
- issue_comment
- check_run
- status
4. Push a Git Tag to Trigger Publishing
The webhook server only processes tag pushes — regular branch pushes are skipped. Any tag format works:
# Semantic version tags
git tag v1.0.0
git push origin v1.0.0
# Tags with slashes
git tag release/v2.0.0
git push origin release/v2.0.0
Note: Branch/tag deletions are automatically ignored — deleting a tag does not trigger a publish.
What Happens During Publishing
When a tag push is received, the server runs these steps in order:
- Checkout — The tagged commit is checked out into a temporary worktree
- Build — Runs
uv build --sdistto create a source distribution - Validate — Runs
twine checkto verify the package metadata - Upload — Runs
twine uploadwith the--skip-existingflag to publish to PyPI
If any step fails, the process stops immediately and a GitHub issue is created in the repository (see Failure Handling below).
Note: The
--skip-existingflag means re-pushing an already-published tag version will not cause an error — the upload is silently skipped.
Slack Notifications on Success
When a package is successfully published and a Slack webhook URL is configured for the repository, a notification is sent:
my-org/my-python-package Version v1.2.0 published to PYPI.
See Setting Up Slack Notifications for how to configure slack-webhook-url.
The python-module-install PR Check
When pypi is configured, the webhook server automatically adds a python-module-install check run to every pull request. This check:
- Runs
pip wheel --no-cache-diragainst the PR branch to verify the package builds correctly - Reports success or failure as a GitHub check run on the PR
- Is automatically added to required status checks for branch protection
This catches packaging errors (missing files, broken pyproject.toml, import issues) before a release tag is pushed, so you won't discover build failures at publish time.
You can re-run this check on a PR with:
/retest python-module-install
See Setting Up CI Checks for more details on CI check configuration.
Failure Handling
If any step in the publish process fails, the webhook server automatically creates a GitHub issue in the repository with:
- Title: A sanitized summary of the error (truncated to 250 characters)
- Body: The full error message from the failing command
This ensures publish failures are visible to the team even if no one is watching the server logs.
Common failure scenarios that trigger issue creation:
| Failure | Cause |
|---|---|
| Checkout failure | Tag doesn't exist or repo can't be cloned |
| Build failure | uv build --sdist fails (e.g., missing pyproject.toml) |
| Twine check failure | Package metadata is invalid |
| Upload failure | Invalid token, network error, or PyPI API issue |
Advanced Usage
Combining PyPI Publishing with Container Builds
PyPI publishing and container builds can both trigger on the same tag push. If both pypi and container (with release: true) are configured, the server runs PyPI upload first, then builds and pushes the container image. A PyPI upload failure stops processing — the container build will not run.
repositories:
my-org/my-python-package:
pypi:
token: pypi-AgEIcH...your-token-here
container:
repository: quay.io/my-org/my-package
username: my-user
password: my-password
release: true
Token Security
The PyPI token is redacted from all command logs. The server passes it as a --password argument to twine upload and registers it as a secret to redact, so even if a command fails and the error output is logged, the token value is masked.
To verify sensitive data masking is active for your repository:
# In config.yaml (global) or .github-webhook-server.yaml (per-repo)
mask-sensitive-data: true # This is the default
Warning: Setting
mask-sensitive-data: falsedisables log redaction for that repository. Only use this temporarily for debugging, and never in production.
Troubleshooting
Package uploads but the wrong version is published
The server checks out the exact commit pointed to by the tag. Make sure your pyproject.toml version matches the tag. Consider using a tool like release-it or setuptools-scm to keep versions in sync.
Issue created with "twine upload failed" error Verify your PyPI token is valid and has upload permissions for the project. Account-scoped tokens work for any project; project-scoped tokens only work for the specified project.
python-module-install check fails on PRs but local builds work
The check runs pip wheel in an isolated environment from the PR branch merged with the base branch. Ensure all build dependencies are declared in pyproject.toml (not just installed locally).
No publish happens when I push a tag
- Confirm
pushis in youreventslist (or thateventsis not set, which subscribes to all events) - Confirm
pypi.tokenis set in your config - Check the server logs for the tag push event