build: move off the yanked claude-agent-sdk 0.1.39 (0.1.39 -> 0.2.157) - #229
Conversation
PyPI has yanked claude-agent-sdk 0.1.39 with the reason "This release will be deleted from PyPI on or after 2026-09-19 to free project storage." That date has passed. The committed lock pins 0.1.39 exactly, and both ci.yml and release.yml install from the lock, so the day PyPI removes the files every build breaks. Bump the constraint to ^0.2.157 and relock. The lock delta is one package: claude-agent-sdk itself. Its new requirements (jsonschema >=4.20.0, sniffio >=1.0.0, mcp >=1.23.0,<3.0.0) are all already satisfied by packages the lock carries, so nothing else moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CH5vbBGwCKYTxkKzwviy8H
CLAUDE_ALLOWED_TOOLS and CLAUDE_DISALLOWED_TOOLS are both Optional, and execute_command passed their value straight through to ClaudeAgentOptions, which declares them as list[str]. 0.1.x tolerated None with a truthiness check. 0.2 does not: the subprocess transport calls list(options.allowed_tools), and the new connect-time shadowing check iterates the same list, so None raises TypeError before the CLI is even started. Normalise both to a list on every path. Behaviour is unchanged -- [] and None are both falsy, so the CLI omits the flags either way -- and the guarded-tool strip no longer needs its None guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CH5vbBGwCKYTxkKzwviy8H
Two claims in ROADMAP-v2.md item 0.1 did not survive contact with 0.2.157: Strict skill-name validation (0.2.129) is not a breaking change for this bot. It applies to ClaudeAgentOptions.skills, a field 0.2 introduced and this bot never sets. It does not touch allowed_tools. CLAUDE_ALLOWED_TOOLS parsing does not need a whitespace audit. src/config/settings.py already strips each entry. The stray space in .env.example was cosmetic, never a bug; tidied here anyway. Rewrite 0.1 to say what the bump actually was, where the real risk sits (the bundled CLI, 2.1.49 to 2.1.277, which the mocked suite cannot cover), and what the live smoke test exercised. Note for #221 that 0.2 emits CanUseToolShadowedWarning at connect naming every tool can_use_tool will never see. Add the CHANGELOG entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CH5vbBGwCKYTxkKzwviy8H
|
CI status: The That is about who opened the pull request, not what is in it. This PR was opened by the
Edit: the maintainer asked for the fix to be carried here rather than split out, so it is now in this PR as The structural point in the struck-through text still stands, though: the workflow runs Generated by Claude Code |
anthropics/claude-code-action refuses a bot-initiated run unless allowed_bots names the account, and it fails the job rather than skipping it. The review check therefore died in ~20s on #229 with `Workflow initiated by non-human actor: claude (type: Bot)`, before reading any code -- the same shape of misleading red check that #227/#228 fixed for `Invalid OIDC token`, at a different gate in the same action. Name claude[bot] in allowed_bots, and gate provenance in the job condition: a bot-opened pull request is reviewed only when its branch lives in this repository. Pushing a branch here takes write access, so the commits came from a collaborator or from an app we installed. Commit author fields are deliberately not the check. `git commit --author` sets them to any name and address, so they prove nothing about who produced the change; who was able to push the branch does. The two gates have to agree: a bot the condition lets through but allowed_bots does not name still fails red. Both name claude[bot], so every other bot -- and any bot PR from a fork -- skips instead. Pull requests from human contributors are unchanged, forks included. Closes #235 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CH5vbBGwCKYTxkKzwviy8H
Move everything under [Unreleased] into a dated [1.8.0] section and open a fresh empty [Unreleased] above it, following the 1.7.0 entry's convention of a short note on why the version number was chosen. `make bump-*` only touches pyproject.toml, so this is the step it does not do. pyproject.toml is deliberately left at 1.7.0: `make bump-minor` takes it to 1.8.0 when the release is cut. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CH5vbBGwCKYTxkKzwviy8H
Requested by Richard · project thread
Description
Before:
poetry.lockpinnedclaude-agent-sdkto exactly 0.1.39. PyPI has that release yanked, with the reason "This release will be deleted from PyPI on or after 2026-09-19 to free project storage." That date has passed; the files still download, but only until PyPI removes them. Since #224 bothci.ymlandrelease.ymlinstall from the committed lock, so the day the files go, every build and every release fails.After: the constraint is
^0.2.157and the lock is regenerated. The lock delta is exactly one package —claude-agent-sdkitself. Its new requirements (jsonschema >=4.20.0,sniffio >=1.0.0,mcp >=1.23.0,<3.0.0) are all already satisfied by packages the lock carries (4.26.0, 1.3.1, 1.26.0), so nothing else moves.The Python API turned out to be a clean widening. Every symbol
src/claude/sdk_integration.pyimports still exists in 0.2.157, including the three private ones it depends on (_errors.MessageParseError,_internal.message_parser.parse_message,types.StreamEvent).ClaudeAgentOptionschanged two annotations, both widenings:system_promptgained union members, andeffort's inline literal became theEffortLevelalias. The bot passes a plainstrfor the first and never sets the second.One real regression did turn up, which is the second commit.
CLAUDE_ALLOWED_TOOLSandCLAUDE_DISALLOWED_TOOLSare bothOptional, andexecute_commandpassed their value straight through toClaudeAgentOptions, which declares them aslist[str]. 0.1.x toleratedNonewith a truthiness check; 0.2 does not — the subprocess transport callslist(options.allowed_tools), and a new connect-time shadowing check iterates the same list, soNoneraisesTypeErrorbefore the CLI is even started. Both values are now normalised to a list on every path. Behaviour is unchanged:[]andNoneare both falsy, so the CLI omits the flags either way.The third commit corrects
docs/ROADMAP-v2.mditem 0.1, which described this bump and got two things wrong:ClaudeAgentOptions.skills, a field 0.2 introduced and this bot never sets. It does not touchallowed_tools.CLAUDE_ALLOWED_TOOLSparsing to strip whitespace.src/config/settings.pyalready doestool.strip()on each entry. The stray space in.env.examplewas cosmetic, never a bug; it is tidied here anyway.The fourth commit is a separate concern, added at the maintainer's request rather than found here — see the scope note below.
anthropics/claude-code-actionrefuses a bot-initiated run unlessallowed_botsnames the account, and it fails the job rather than skipping it, so thereviewcheck on this PR died in 20 seconds withWorkflow initiated by non-human actorbefore reading any code.allowed_botsnow namesclaude[bot], and the job condition admits a bot-opened PR only when its branch lives in this repository:Pushing a branch to this repository takes write access, so those commits came from a collaborator or from an app we installed. Commit author fields are deliberately not the check —
git commit --authorsets them to any name and address, so they prove nothing about who produced a change; who was able to push the branch does. The two gates have to agree, because a bot the condition lets through butallowed_botsdoes not name would still fail red. Both nameclaude[bot], so every other bot, and any bot PR from a fork, skips instead of failing.dependabot[bot]is deliberately not admitted yet: its branches are in-repo and would qualify, but GitHub treats secrets differently for Dependabot-triggered runs and that wants checking before claiming it works.Note this does not fix this PR's own
reviewcheck. The workflow runson: pull_request_target, so its definition is always read from the default branch; the change takes effect only once merged.The fifth commit cuts the changelog for 1.8.0, also at the maintainer's request, since a release follows this merge. Everything that was under
[Unreleased]moves into a dated[1.8.0]section with a short note on why minor rather than patch, and a fresh empty[Unreleased]opens above it.pyproject.tomlis deliberately left at 1.7.0 —make bump-minoris what moves it, and pre-bumping here would land the release on 1.9.0 instead.How: the bump itself is
pyproject.tomlpluspoetry lock. TheNonefix is four lines inexecute_commandplus a regression test. The roadmap rewrite records what the bump actually was and where the risk actually sits. The workflow change is one input and one condition. The changelog cut is a heading move.Related issue
Closes #235
The SDK bump itself has no issue — it is
docs/ROADMAP-v2.mditem 0.1, brought forward because the yank made it urgent.Type of change
How it was tested
The suite mocks the SDK almost completely —
tests/unit/test_claude/test_sdk_integration.pyandtests/unit/test_bot/test_stop_button.pyare the only files that import it, and both patch heavily — so a green suite shows the types line up, not that the runtime behaves. That matters here because the SDK bundles the Claude Code CLI, which jumps from 2.1.49 to 2.1.277.Automated, on 0.2.157, with no source changes beyond this PR:
black --check src tests— clean, 122 filesisort --check-only src tests— cleanflake8 src tests— 0 issuespytest— 589 passed (588 before, plus the new regression test)poetry check --lock— passesmypyis not run: CI does not gate on it andmainitself carries pre-existing errors.By hand, against the real bundled CLI — 7 of 7 passed. Note what this was and was not: I do not have a Telegram bot token in this environment, so there is no live Telegram bot in these results. What I did instead was drive the real
ClaudeSDKManagerdirectly against the real bundled CLI with no mocks, which exercises the SDK boundary — the only thing this PR moves. The Telegram transport above it is untouched by this change, but it is also unverified here. Worth a few minutes against a real bot before the release is tagged.ResultMessage, resumed withcontinue_session=True, and the resumed session recalled the codeword from the first turncan_use_tooldenial for a path outsideAPPROVED_DIRECTORYReadon/etc/hostname→PermissionResultDeny("Access denied: path outside approved directory")touch /tmp/smoke-escape.txt→PermissionResultDeny("Directory boundary violation: 'touch' targets '/tmp/smoke-escape.txt' which is outside approved directory ..."), and the file was not createdinterrupt_eventset 8s into a long run; the response came back withinterrupted=Truestream_callbackreceived 8 updates acrossassistant,userandstream_delta— this is theinclude_partial_messagespath thatVERBOSE_LEVEL=1renders, exercised at the SDK boundary rather than through TelegramINTERACTIVE_TOOL_APPROVAL=true; the callback was prompted forWriteand allowed it; the file was writtenPermissionResultDeny("Denied by user via Telegram")and no fileThe workflow change could not be run.
pull_request_targetreads the workflow from the default branch, so this change cannot execute until it is merged — that is the same property that makes it unable to fix this PR's own red check. What was checked instead: the file parses as YAML, and the condition was evaluated against every case it has to get right.claude[bot], in-repo branchclaude[bot], fork branchdependabot[bot], in-repo branchNo case lands on red.
One thing worth recording from the smoke run, because it is directly relevant to #221. With
can_use_toolset, 0.2 emits aCanUseToolShadowedWarningat connect time naming every tool the callback will never see. On a default install that is:That is the SDK detecting by itself the condition #219 reported and #220 fixed by hand. Not acted on here — it is follow-up work, filed separately.
make testandmake lintpass locallyChecklist
claude-code-review.ymlfix, and the 1.8.0 changelog cut. The last two were requested by the maintainer to be carried here rather than split out. Flagging rather than quietly ticking the box.CHANGELOG.mdhas an entry — now under the dated[1.8.0]heading rather than[Unreleased], since this merge is being releasedpyproject.tomldependencies changed,poetry lockwas run and the updatedpoetry.lockis committedREADME.md,docs/,.env.example,CLAUDE.md) where settings or commands changed🤖 Generated with Claude Code
https://claude.ai/code/session_01CH5vbBGwCKYTxkKzwviy8H