Skip to content

Conversation

@ashwin-ant
Copy link
Collaborator

@ashwin-ant ashwin-ant commented Jan 17, 2026

Summary

Goal: Fully Automated SDK Releases on CLI Bumps

When the bundled CLI version is bumped (e.g., chore: bump bundled CLI version to 2.1.12), we want the SDK to automatically publish a new patch version to PyPI without any manual intervention. This PR implements that automation.

How it works

Push: "chore: bump bundled CLI version to X.Y.Z"
         ↓
    Test workflow runs (lint, tests, e2e)
         ↓ (on success)
    auto-release.yml triggers
         ↓
    ┌─ Verify commit message + _cli_version.py changed
    ├─ Calculate next version: 0.1.20 → 0.1.21
    ├─ Build 4 platform wheels (linux, linux-arm, macos, windows)
    ├─ Publish to PyPI
    ├─ Update _version.py, pyproject.toml
    ├─ Generate changelog with Claude
    ├─ Push directly to main (via deploy key)
    └─ Create git tag + GitHub Release

Changes

  • auto-release.yml (new): Triggers on CLI bump commits after Test workflow passes. Uses deploy key to push directly to main.
  • build-and-publish.yml (new): Reusable workflow for building wheels and publishing. Supports both direct-push (auto) and PR (manual) flows.
  • publish.yml (updated): Now calls the reusable workflow. Still available for manual releases (SDK-only changes, major/minor bumps).
  • Both publish flows now use the production environment for secrets (DEPLOY_KEY, PYPI_API_TOKEN).

Setup Required

  1. Create production environment in repo settings
  2. Add DEPLOY_KEY secret (SSH deploy key with write access)
  3. Move PYPI_API_TOKEN to production environment

Test plan

  • Verify workflow syntax is valid in GitHub Actions
  • Test with actual CLI version bump commit
  • Confirm wheels build on all 4 platforms
  • Verify PyPI publishing and GitHub release creation

Changelog

N/A - Internal CI/CD workflow changes only

🤖 Generated with Claude Code

- Refactor publish.yml to use a reusable workflow for build and publish steps
- Add new build-and-publish.yml as a reusable workflow that handles wheel building, PyPI publishing, and PR creation
- Add auto-release.yml for automated releases triggered by version changes
- Simplify publish.yml to only handle tests, lint, and orchestration

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 17
Claude-Permission-Prompts: 1
Claude-Escapes: 1
Claude-Plan:
<claude-plan>
# Automated Release on CLI Version Bump

## Overview

Create a new workflow that automatically publishes to PyPI when a CLI version bump commit passes CI.

**Trigger**: Commit message starting with `chore: bump bundled CLI version to` pushed to main, after Test workflow succeeds.

**Actions**:
1. Auto-increment SDK patch version (0.1.20 → 0.1.21)
2. Build platform-specific wheels (linux, linux-arm, macos, windows)
3. Publish to PyPI
4. Update version files + changelog
5. Push directly to main (via deploy key)
6. Create git tag + GitHub Release

## Files to Create/Modify

### New: `.github/workflows/auto-release.yml`

```yaml
name: Auto Release on CLI Bump

on:
  workflow_run:
    workflows: ["Test"]
    types: [completed]
    branches: [main]

jobs:
  check-trigger:
    runs-on: ubuntu-latest
    if: |
      github.event.workflow_run.conclusion == 'success' &&
      github.event.workflow_run.event == 'push' &&
      startsWith(github.event.workflow_run.head_commit.message, 'chore: bump bundled CLI version to')
    outputs:
      version: ${{ steps.version.outputs.version }}
      previous_tag: ${{ steps.previous_tag.outputs.previous_tag }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get current SDK version and calculate next
        id: version
        run: |
          CURRENT=$(python -c "import re; print(re.search(r'__version__ = \"([^\"]+)\"', open('src/claude_agent_sdk/_version.py').read()).group(1))")
          IFS='.' read -ra PARTS <<< "$CURRENT"
          NEXT="${PARTS[0]}.${PARTS[1]}.$((PARTS[2] + 1))"
          echo "version=$NEXT" >> $GITHUB_OUTPUT
          echo "Current: $CURRENT -> Next: $NEXT"

      - name: Get previous release tag
        id: previous_tag
        run: |
          PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
          echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT

  build-wheels:
    needs: check-trigger
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install build dependencies
        run: pip install build twine wheel
        shell: bash

      - name: Build wheel with bundled CLI
        run: python scripts/build_wheel.py --version "${{ needs.check-trigger.outputs.version }}" --skip-sdist --clean
        shell: bash

      - uses: actions/upload-artifact@v4
        with:
          name: wheel-${{ matrix.os }}
          path: dist/*.whl
          if-no-files-found: error

  publish:
    needs: [check-trigger, build-wheels]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    env:
      VERSION: ${{ needs.check-trigger.outputs.version }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ssh-key: ${{ secrets.DEPLOY_KEY }}

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Update version files
        run: python scripts/update_version.py "$VERSION"

      - uses: actions/download-artifact@v4
        with:
          path: dist
          pattern: wheel-*
          merge-multiple: true

      - name: Build sdist and publish to PyPI
        run: |
          pip install build twine
          python -m build --sdist
          twine upload dist/*
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

      - name: Commit version changes
        run: |
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git config user.name "github-actions[bot]"
          git add pyproject.toml src/claude_agent_sdk/_version.py
          git commit -m "chore: release v$VERSION"

      - name: Update changelog with Claude
        continue-on-error: true
        uses: anthropics/claude-code-action@v1
        with:
          prompt: "/generate-changelog new version: ${{ env.VERSION }}, old version: ${{ needs.check-trigger.outputs.previous_tag }}"
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          claude_args: |
            --model claude-opus-4-5
            --allowedTools 'Bash(git add:*),Bash(git commit:*),Edit'

      - name: Push to main
        run: git push origin main

      - name: Create tag and GitHub Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git tag -a "v$VERSION" -m "Release v$VERSION"
          git push origin "v$VERSION"

          # Extract changelog section
          awk -v ver="$VERSION" '/^## / { if (found) exit; if ($2 == ver) found=1; next } found { print }' CHANGELOG.md > release_notes.md
          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
```

### Keep Existing: `publish.yml`

Keep as-is for manual releases (SDK-only changes, major/minor bumps, emergencies).

### Optional Cleanup: `create-release-tag.yml`

Can be removed since auto-release handles tagging. Or keep as fallback for manual publish flow.

## Setup Required

### Deploy Key (Required)

1. Generate SSH key:
   ```bash
   ssh-keygen -t ed25519 -C "github-actions-deploy-key" -f deploy_key -N ""
   ```

2. Add public key as deploy key:
   - Repo Settings → Deploy keys → Add deploy key
   - Title: "GitHub Actions Auto Release"
   - Paste `deploy_key.pub` contents
   - Check "Allow write access"

3. Add private key as secret:
   - Repo Settings → Secrets → New repository secret
   - Name: `DEPLOY_KEY`
   - Value: `deploy_key` contents

4. Delete local key files

### Existing Secrets (Already Present)

- `PYPI_API_TOKEN` - PyPI publishing
- `ANTHROPIC_API_KEY` - Changelog generation

## Flow Diagram

```
Push: "chore: bump bundled CLI version to 2.1.12"
         ↓
    lint.yml + test.yml (parallel)
         ↓ (workflow_run)
    auto-release.yml
         ↓
    ┌─ check conditions (CI passed, commit pattern matches)
    ├─ calculate version: 0.1.20 → 0.1.21
    ├─ build 4 platform wheels
    ├─ publish to PyPI
    ├─ update _version.py, pyproject.toml
    ├─ generate changelog
    ├─ push to main
    ├─ create tag v0.1.21
    └─ create GitHub Release
```

## Verification

After implementation:
1. Create a test branch with the workflow
2. Add `workflow_dispatch` trigger temporarily for testing
3. Run manually and verify each step
4. Remove `workflow_dispatch`, merge to main
5. Test with an actual CLI version bump commit
</claude-plan>
@ashwin-ant ashwin-ant requested a review from a team January 17, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants