diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 2ca08d3e..43dcf017 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -47,10 +47,8 @@ jobs: needs: check-trigger permissions: contents: write - pull-requests: write uses: ./.github/workflows/build-and-publish.yml with: version: ${{ needs.check-trigger.outputs.version }} previous_tag: ${{ needs.check-trigger.outputs.previous_tag }} - push_directly: true secrets: inherit diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 62262d00..267c286f 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -12,11 +12,6 @@ on: required: false type: string default: '' - push_directly: - description: 'Push directly to main (true) or create PR (false)' - required: false - type: boolean - default: false jobs: build-wheels: @@ -51,22 +46,14 @@ jobs: environment: production permissions: contents: write - pull-requests: write env: VERSION: ${{ inputs.version }} steps: - uses: actions/checkout@v4 - if: inputs.push_directly with: fetch-depth: 0 ssh-key: ${{ secrets.DEPLOY_KEY }} - - uses: actions/checkout@v4 - if: ${{ !inputs.push_directly }} - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/setup-python@v5 with: python-version: '3.12' @@ -74,12 +61,6 @@ jobs: - name: Update version files run: python scripts/update_version.py "$VERSION" - - name: Read CLI version from code - id: cli_version - run: | - CLI_VERSION=$(python -c "import re; print(re.search(r'__cli_version__ = \"([^\"]+)\"', open('src/claude_agent_sdk/_cli_version.py').read()).group(1))") - echo "cli_version=$CLI_VERSION" >> $GITHUB_OUTPUT - - uses: actions/download-artifact@v4 with: path: dist @@ -116,15 +97,12 @@ jobs: --model claude-opus-4-5 --allowedTools 'Bash(git add:*),Bash(git commit:*),Edit' - # Direct push flow (auto-release) - name: Push to main - if: inputs.push_directly run: | git remote set-url origin git@github.com:anthropics/claude-agent-sdk-python.git git push origin main - name: Create tag and GitHub Release - if: inputs.push_directly env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -135,38 +113,3 @@ jobs: echo -e "\n---\n\n**PyPI:** https://pypi.org/project/claude-agent-sdk/$VERSION/\n\n\`\`\`bash\npip install claude-agent-sdk==$VERSION\n\`\`\`" >> release_notes.md gh release create "v$VERSION" --title "v$VERSION" --notes-file release_notes.md - - # PR flow (manual publish) - - name: Create release branch - if: ${{ !inputs.push_directly }} - run: | - BRANCH_NAME="release/v$VERSION" - echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV - git checkout -b "$BRANCH_NAME" - - - name: Push branch and create PR - if: ${{ !inputs.push_directly }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git push origin "$BRANCH_NAME" - - PR_BODY="This PR updates the version to $VERSION after publishing to PyPI. - - ## Changes - - Updated version in \`pyproject.toml\` to $VERSION - - Updated version in \`src/claude_agent_sdk/_version.py\` to $VERSION - - Updated \`CHANGELOG.md\` with release notes - - ## Release Information - - Published to PyPI: https://pypi.org/project/claude-agent-sdk/$VERSION/ - - Bundled CLI version: ${{ steps.cli_version.outputs.cli_version }} - - Install with: \`pip install claude-agent-sdk==$VERSION\` - - 🤖 Generated by GitHub Actions" - - gh pr create \ - --title "chore: release v$VERSION" \ - --body "$PR_BODY" \ - --base main \ - --head "$BRANCH_NAME" diff --git a/.github/workflows/create-release-tag.yml b/.github/workflows/create-release-tag.yml deleted file mode 100644 index c50abab1..00000000 --- a/.github/workflows/create-release-tag.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Create Release Tag - -on: - pull_request: - types: [closed] - branches: [main] - -jobs: - create-tag: - if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/v') - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Extract version from branch name - id: extract_version - run: | - BRANCH_NAME="${{ github.event.pull_request.head.ref }}" - VERSION="${BRANCH_NAME#release/v}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Create and push tag - run: | - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - - # Create annotated tag - git tag -a "v${{ steps.extract_version.outputs.version }}" \ - -m "Release v${{ steps.extract_version.outputs.version }}" - - # Push tag - git push origin "v${{ steps.extract_version.outputs.version }}" - - - name: Create GitHub Release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - VERSION="${{ steps.extract_version.outputs.version }}" - - # Extract changelog section for this version to a temp file - awk -v ver="$VERSION" ' - /^## / { - if (found) exit - if ($2 == ver) found=1 - next - } - found { print } - ' CHANGELOG.md > release_notes.md - - # Append install instructions - { - echo "" - echo "---" - echo "" - echo "**PyPI:** https://pypi.org/project/claude-agent-sdk/VERSION/" - echo "" - echo '```bash' - echo "pip install claude-agent-sdk==VERSION" - echo '```' - } >> release_notes.md - - # Replace VERSION placeholder - sed -i "s/VERSION/$VERSION/g" release_notes.md - - # Create release with notes from file - gh release create "v$VERSION" \ - --title "v$VERSION" \ - --notes-file release_notes.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5be99544..9b5edb44 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -74,9 +74,10 @@ jobs: release: needs: [test, lint, get-previous-tag] + permissions: + contents: write uses: ./.github/workflows/build-and-publish.yml with: version: ${{ github.event.inputs.version }} previous_tag: ${{ needs.get-previous-tag.outputs.previous_tag }} - push_directly: false secrets: inherit diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..a2b31930 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,63 @@ +# Releasing + +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 4 OS targets (Ubuntu x86, Ubuntu ARM, macOS, Windows), publishes to PyPI, updates version files, generates a changelog entry using Claude, pushes to `main`, and creates a git tag + GitHub Release. + +## Versioning + +The project tracks two separate version numbers: + +- **SDK version** — in `pyproject.toml` and `src/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`. + +## Automatic Release (CLI Version Bump) + +This is the most common release path. Every CLI version bump automatically produces a new SDK patch release. + +**Flow:** + +1. A commit with message `chore: bump bundled CLI version to X.Y.Z` is pushed to `main`, updating `_cli_version.py`. +2. The `Test` workflow runs on that push. +3. On successful completion, `auto-release.yml` fires via `workflow_run`. +4. It verifies the trigger commit message and that `_cli_version.py` changed. +5. It reads the current SDK version from `_version.py` and increments the patch number (e.g., `0.1.24` → `0.1.25`). +6. 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 +``` + +## Manual Release + +Use this when you need to release with a specific version number (e.g., for minor/major bumps or non-CLI-bump changes). + +**Flow:** + +1. Go to [**Actions → Publish to PyPI**](https://github.com/anthropics/claude-agent-sdk-python/actions/workflows/publish.yml) and click **Run workflow**. +2. Enter the desired version (e.g., `0.2.0`). +3. The workflow runs the full test suite (Python 3.10–3.13) and lint checks. +4. On success, it calls `build-and-publish.yml`, which builds, publishes, pushes, tags, and creates a GitHub Release. + +## Scripts + +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 | + +## Required Secrets + +| Secret | Used For | +|---|---| +| `PYPI_API_TOKEN` | Publishing to PyPI | +| `ANTHROPIC_API_KEY` | Changelog generation and e2e tests | +| `DEPLOY_KEY` | SSH key for direct pushes to `main` |