Skip to content

feat: automated SDK version bump workflow with feature analysis#987

Open
jeremyeder wants to merge 3 commits intomainfrom
automation/sdk-version-bump
Open

feat: automated SDK version bump workflow with feature analysis#987
jeremyeder wants to merge 3 commits intomainfrom
automation/sdk-version-bump

Conversation

@jeremyeder
Copy link
Copy Markdown
Contributor

Summary

Adds a daily GHA workflow that automatically detects new releases of claude-agent-sdk and anthropic on PyPI, bumps pyproject.toml + uv.lock, and opens a PR with an intelligent feature analysis report.

  • scripts/sdk-version-bump.py — orchestrates PyPI checking, pyproject.toml updates, uv lock, and GitHub changelog fetching
  • scripts/sdk_report.py — parses release notes into structured features with status/action/runner-relevance detection
  • .github/workflows/sdk-version-bump.yml — daily 9 AM UTC schedule + manual workflow_dispatch
  • 38 tests covering parsing (both SDK changelog formats), feature detection, report rendering, and version management

Report format

The generated PR body includes:

  • TL;DR — counts of migrations, opt-in features, behavior changes, bug fixes
  • Action Items — only migrations and opt-in checkboxes (the stuff that needs attention)
  • Behavior Changes — compact table
  • Collapsible details — full feature table, bug fixes, CLI versions, internal changes

How it works

Schedule (daily) → Check PyPI → Update pyproject.toml → uv lock →
Fetch GitHub changelogs → Parse & analyze features → Create/update PR

One PR per bump cycle. Force-pushes to automated/sdk-version-bump branch. Detects and updates existing open PRs.

Test plan

  • 38 unit tests pass (pytest scripts/tests/)
  • Manual workflow_dispatch run on this branch
  • Verify PR body renders correctly on GitHub

🤖 Generated with Claude Code

Add daily GHA workflow that checks PyPI for new releases of
claude-agent-sdk and anthropic, bumps pyproject.toml + uv.lock,
and creates a PR with a concise feature analysis report.

Components:
- scripts/sdk-version-bump.py: PyPI checking, version updating, changelog fetching
- scripts/sdk_report.py: release note parser with feature/status/action detection
- .github/workflows/sdk-version-bump.yml: daily schedule + manual trigger
- 38 tests covering parsing, detection, rendering, and orchestration

The report generates a TL;DR, action items (migrate/opt-in), behavior
changes, and collapsible details for features/bugfixes/CLI versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jeremyeder jeremyeder added dependencies Pull requests that update a dependency file automated labels Mar 23, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Walkthrough

Adds an automated SDK dependency updater: a GitHub Actions workflow plus scripts that detect PyPI SDK updates, update runner dependency constraints, regenerate lockfiles, build feature-change reports from GitHub release notes, and open or update a PR with the changes.

Changes

Cohort / File(s) Summary
GitHub Actions Workflow
.github/workflows/sdk-version-bump.yml
New scheduled & manual workflow driving the bump process: runs check-only, invokes the bump script, commits components/runners/ambient-runner/pyproject.toml and uv.lock, and creates/updates an automated/sdk-version-bump PR with generated title/body and labels.
Ignore Configuration
.gitignore
Adds .sdk-bump-output/ to ignore transient script outputs.
Documentation / Bookmarks
BOOKMARKS.md
Adds "Dependency Automation" section linking the workflow, version-bump script, and report-generation script.
Version-bump CLI
scripts/sdk-version-bump.py
New CLI that detects newer PyPI versions for tracked packages, compares to runner pyproject.toml, supports --check-only, updates pyproject.toml, runs uv lock, fetches GitHub releases for changed versions, and writes PR title/body to .sdk-bump-output/.
Report generator
scripts/sdk_report.py
New module that parses GitHub release-note markdown into structured Feature records (status, runner relevance, action), aggregates them into FeatureReport(s), and renders a markdown PR report.
Unit tests
scripts/tests/...
scripts/tests/test_sdk_report.py, scripts/tests/test_sdk_version_bump.py
Adds pytest suites covering version parsing/comparison, pyproject dependency extraction/updating, PyPI/GitHub fetch behaviors, changelog parsing heuristics, report construction, and markdown rendering.

Sequence Diagram(s)

sequenceDiagram
    participant GHA as "GitHub Actions (workflow)"
    participant CLI as "sdk-version-bump.py"
    participant PyPI as "PyPI API"
    participant UV as "uv (lock)"
    participant GitHub as "GitHub API (releases / PRs)"
    participant Report as "sdk_report.py"

    GHA->>CLI: Run (--check-only)
    CLI->>PyPI: Query latest package versions
    PyPI-->>CLI: Latest versions
    CLI-->>GHA: Exit code (0=no, 2=updates)

    alt Updates available
        GHA->>GitHub: Check existing PR (head=automated/sdk-version-bump)
        GHA->>CLI: Run apply (no --check-only)
        CLI->>CLI: Update `pyproject.toml`
        CLI->>UV: Run `uv lock` in runner dir
        UV-->>CLI: `uv.lock` regenerated
        CLI->>GitHub: Fetch releases between versions
        GitHub-->>CLI: Release bodies
        CLI->>Report: build_report(from,to,entries)
        Report-->>CLI: FeatureReport(s)
        CLI->>Report: render_report_markdown()
        Report-->>CLI: PR body markdown
        CLI-->>GHA: Write `.sdk-bump-output/` (title, body)
        GHA->>GitHub: Create or update PR with files and body
        GitHub-->>GHA: PR link / result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: introducing an automated SDK version bump workflow with feature analysis. It accurately reflects the primary objective of the PR.
Description check ✅ Passed Description is directly related to the changeset, providing a detailed summary of the workflow, supporting scripts, report format, and test coverage. It clearly explains the purpose and implementation of the automated SDK version bump system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch automation/sdk-version-bump

Comment @coderabbitai help to get the list of available commands and usage tips.

@jeremyeder jeremyeder marked this pull request as ready for review March 24, 2026 02:12
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/sdk-version-bump.yml:
- Around line 129-145: Replace passing the PR body via the shell variable
PR_BODY to avoid argument length limits by using gh's --body-file option with
the existing file .sdk-bump-output/pr-body.md; update both gh pr edit (when
steps.existing_pr.outputs.pr_exists == "true") and gh pr create calls to use
--body-file .sdk-bump-output/pr-body.md instead of --body "$PR_BODY" (keeping
--title "$PR_TITLE", --base main, --head "$BRANCH" and --label unchanged) so the
PR body is read from the file rather than passed as a long shell argument.

In `@scripts/sdk_report.py`:
- Around line 97-191: Extract the inline header classification in
parse_release_body into a helper function (e.g.,
_classify_section_header(header: str) -> str) that lowercases the header,
returns "" for version-like headers (re.match(r"\d+\.\d+", header)), and maps
substrings to section keys ("feature", "bugfix", "breaking", "deprecated",
"behavior_change", "internal") as shown in the review; then replace the block
that sets current_section inside parse_release_body (the code that computes
prefix_len, header, and the series of if/elif checks) with a call to this helper
and assign its return to current_section. Ensure the helper is used wherever
header classification occurs and keep semantics identical (skip version headers
by returning empty string).
- Around line 275-285: The function _detect_relevance currently defaults to
RunnerRelevance.CLAUDE when no keywords match, which mislabels runner-agnostic
features; update the final return so that when neither is_claude nor is_gemini
is true it returns a neutral value (prefer RunnerRelevance.NONE if that enum
member exists, otherwise RunnerRelevance.BOTH) and ensure any callers relying on
CLAUDE are updated accordingly; change the return in _detect_relevance and
adjust tests or uses of RunnerRelevance to reflect the new default.

In `@scripts/sdk-version-bump.py`:
- Around line 151-162: The subprocess invocation that runs the uv lock command
should explicitly set check=False to make the intent clear and satisfy linters:
update the subprocess.run call that creates `result` (the call using
subprocess.run(["uv", "lock"], cwd=runner_dir, capture_output=True, text=True,
timeout=120)) to include check=False so the code continues to manually inspect
`result.returncode`.
- Around line 213-218: The _version_tuple function currently returns (0,) for
versions with pre-release suffixes like "1.0.0rc1"; update _version_tuple to
parse each dot-separated segment by extracting the leading numeric portion
(e.g., via a regex like r"^(\d+)") and convert those leading digits to ints,
treating missing or non-numeric segments as 0, so "1.0.0rc1" => (1,0,0); ensure
the function still catches invalid inputs (ValueError/AttributeError) and
returns a sensible default for those cases.
- Line 178: The current releases fetch builds a single URL in the variable url =
f"https://api.github.com/repos/{github_repo}/releases?per_page=100" which only
returns up to 100 releases; update the logic that calls this URL (the
releases-fetching function) to implement GitHub pagination: repeatedly request
pages using either the page parameter (e.g., per_page=100&page=N) or follow the
Link header until no more pages, aggregating all release items into a single
list before continuing processing; ensure you handle rate limits and stop when
an empty page or no next link is returned.

In `@scripts/tests/test_sdk_version_bump.py`:
- Around line 267-279: build_pr_title produces a malformed title when no
VersionInfo items have needs_update=True; modify build_pr_title to filter
versions by needs_update (use VersionInfo.needs_update) and if the filtered list
is empty return a clear sentinel (e.g. "deps(runner): no updates") instead of
constructing a title with a trailing space, otherwise build the existing bump
title from the filtered package names as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 61115cbe-e50e-4131-ab02-9a30afa3ab45

📥 Commits

Reviewing files that changed from the base of the PR and between 6b21778 and 6d5091e.

📒 Files selected for processing (7)
  • .github/workflows/sdk-version-bump.yml
  • .gitignore
  • BOOKMARKS.md
  • scripts/sdk-version-bump.py
  • scripts/sdk_report.py
  • scripts/tests/test_sdk_report.py
  • scripts/tests/test_sdk_version_bump.py

- Use --body-file in GHA workflow to avoid arg length limits
- Extract _classify_section_header helper in sdk_report.py
- Fix _detect_relevance to default to NONE instead of CLAUDE
- Add check=False to subprocess.run in regenerate_lockfile
- Handle pre-release version suffixes in _version_tuple
- Add GitHub releases pagination via _fetch_all_releases
- Guard build_pr_title against empty updates list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/sdk-version-bump.yml:
- Around line 50-62: The run block can exit early due to `set -e` so
`EXIT_CODE=$?` may never run; change the block around the `python
scripts/sdk-version-bump.py --check-only --package "$PACKAGE_ARG"` invocation
(referencing PACKAGE_ARG and EXIT_CODE) to disable errexit before running the
script (or append a safe fallback) so you can reliably capture its exit code,
then re-enable errexit if needed; specifically, temporarily turn off `errexit`
(or run the python command with a suffix like `|| true`) so the script can
return 2/0, assign `EXIT_CODE=$?`, and then handle the three cases as shown.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 03f01d3f-6e82-4c10-aa67-7ebc4323e64a

📥 Commits

Reviewing files that changed from the base of the PR and between 6d5091e and 43d1c61.

📒 Files selected for processing (4)
  • .github/workflows/sdk-version-bump.yml
  • scripts/sdk-version-bump.py
  • scripts/sdk_report.py
  • scripts/tests/test_sdk_version_bump.py

Comment on lines +50 to +62
run: |
PACKAGE_ARG="${{ inputs.package || 'all' }}"
python scripts/sdk-version-bump.py --check-only --package "$PACKAGE_ARG"
EXIT_CODE=$?

if [ $EXIT_CODE -eq 2 ]; then
echo "updates_available=true" >> $GITHUB_OUTPUT
elif [ $EXIT_CODE -eq 0 ]; then
echo "updates_available=false" >> $GITHUB_OUTPUT
else
echo "Script failed with exit code $EXIT_CODE"
exit 1
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Exit code capture may fail due to shell errexit behavior.

The script's exit code 2 (updates available) will cause the shell to exit immediately before EXIT_CODE=$? executes, since GitHub Actions run blocks have set -e by default.

🛡️ Proposed fix to properly capture exit codes
       - name: Check for SDK updates
         id: check
         run: |
           PACKAGE_ARG="${{ inputs.package || 'all' }}"
-          python scripts/sdk-version-bump.py --check-only --package "$PACKAGE_ARG"
-          EXIT_CODE=$?
+          set +e
+          python scripts/sdk-version-bump.py --check-only --package "$PACKAGE_ARG"
+          EXIT_CODE=$?
+          set -e
 
           if [ $EXIT_CODE -eq 2 ]; then
             echo "updates_available=true" >> $GITHUB_OUTPUT
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/sdk-version-bump.yml around lines 50 - 62, The run block
can exit early due to `set -e` so `EXIT_CODE=$?` may never run; change the block
around the `python scripts/sdk-version-bump.py --check-only --package
"$PACKAGE_ARG"` invocation (referencing PACKAGE_ARG and EXIT_CODE) to disable
errexit before running the script (or append a safe fallback) so you can
reliably capture its exit code, then re-enable errexit if needed; specifically,
temporarily turn off `errexit` (or run the python command with a suffix like `||
true`) so the script can return 2/0, assign `EXIT_CODE=$?`, and then handle the
three cases as shown.

@ambient-code ambient-code bot added this to the Review Queue milestone Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant