There are two ways to release the SDK: automatic (triggered by a CLI version bump) and manual (triggered via GitHub Actions UI).
Both flows call the same reusable build-and-publish.yml workflow, which builds platform-specific wheels on 5 OS targets, publishes to PyPI, updates version files, generates a changelog entry using Claude, pushes to main, and creates a git tag + GitHub Release.
Wheel targets:
| Runner | Platform tag |
|---|---|
ubuntu-latest |
manylinux_2_17_x86_64 |
ubuntu-24.04-arm |
manylinux_2_17_aarch64 |
macos-latest |
macosx_11_0_arm64 |
macos-15-intel |
macosx_11_0_x86_64 |
windows-latest |
win_amd64 |
PRs that touch the build scripts, pyproject.toml, or the publish workflow trigger build-wheel-check.yml, which dry-runs the full build matrix and verifies each wheel contains the bundled CLI before merge.
PyPI rejects any file over this project's per-file limit of 250 MiB (raised from the default 100 MiB in pypi/support#12263). A wheel over that limit does not block the release: the publish job leaves it out, shows a warning on the run, and publishes the sdist and the other wheels. pip install on the platform that was left out gets the sdist, which has no bundled CLI, so the SDK there uses a separately installed Claude Code. The wheel stays in the run's build artifacts. The release still fails if the sdist or every wheel is over the limit.
The project tracks two separate version numbers:
- SDK version — in
pyproject.tomlandsrc/claude_agent_sdk/_version.py - Bundled CLI version — in
src/claude_agent_sdk/_cli_version.py
Both follow semver (MAJOR.MINOR.PATCH). Git tags use the format vX.Y.Z.
This is the most common release path. Every CLI version bump automatically produces a new SDK patch release.
Flow:
- A commit with message
chore: bump bundled CLI version to X.Y.Zis pushed tomain, updating_cli_version.py. - The
Testworkflow runs on that push. - On successful completion,
auto-release.ymlfires viaworkflow_run. - It verifies the trigger commit message and that
_cli_version.pychanged. - It reads the current SDK version from
_version.pyand increments the patch number (e.g.,0.1.24→0.1.25). - It calls
build-and-publish.yml, which builds, publishes, pushes, tags, and creates a GitHub Release.
Typical commit log after an auto-release:
ccdf20a chore: bump bundled CLI version to 2.1.25
baf9bc3 chore: release v0.1.25
Use this when you need to release with a specific version number (e.g., for minor/major bumps or non-CLI-bump changes).
Flow:
- Go to Actions → Publish to PyPI and click Run workflow.
- Enter the desired version (e.g.,
0.2.0). - The workflow runs the full test suite (Python 3.10–3.13) and lint checks.
- On success, it calls
build-and-publish.yml, which builds, publishes, pushes, tags, and creates a GitHub Release.
All release-related scripts live in scripts/:
| Script | Purpose |
|---|---|
update_version.py |
Updates SDK version in pyproject.toml and _version.py |
update_cli_version.py |
Updates CLI version in _cli_version.py |
build_wheel.py |
Downloads the CLI binary, builds the wheel, retags with platform-specific tags |
download_cli.py |
Downloads the Claude Code CLI binary for the current platform |
| Secret | Used For |
|---|---|
ANTHROPIC_API_KEY |
Changelog generation and e2e tests |
DEPLOY_KEY |
SSH key for direct pushes to main |
Publishing to PyPI needs no secret: it uses PyPI trusted publishing. The
claude-agent-sdk project on PyPI trusts build-and-publish.yml, the reusable
workflow whose publish job does the upload, in the production environment.
PyPI checks the workflow that runs the upload job, not the one that called it,
so publish.yml and auto-release.yml need no entry of their own. Renaming
build-and-publish.yml, or moving the upload to another workflow or out of the
production environment, needs the matching change on PyPI.