feat: automated SDK version bump workflow with feature analysis#987
feat: automated SDK version bump workflow with feature analysis#987jeremyeder wants to merge 3 commits intomainfrom
Conversation
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>
WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
.github/workflows/sdk-version-bump.yml.gitignoreBOOKMARKS.mdscripts/sdk-version-bump.pyscripts/sdk_report.pyscripts/tests/test_sdk_report.pyscripts/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>
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
.github/workflows/sdk-version-bump.ymlscripts/sdk-version-bump.pyscripts/sdk_report.pyscripts/tests/test_sdk_version_bump.py
| 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 |
There was a problem hiding this comment.
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.
Summary
Adds a daily GHA workflow that automatically detects new releases of
claude-agent-sdkandanthropicon PyPI, bumpspyproject.toml+uv.lock, and opens a PR with an intelligent feature analysis report.scripts/sdk-version-bump.py— orchestrates PyPI checking,pyproject.tomlupdates,uv lock, and GitHub changelog fetchingscripts/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 + manualworkflow_dispatchReport format
The generated PR body includes:
How it works
One PR per bump cycle. Force-pushes to
automated/sdk-version-bumpbranch. Detects and updates existing open PRs.Test plan
pytest scripts/tests/)workflow_dispatchrun on this branch🤖 Generated with Claude Code