Repository navigation
fix(deps): require anyio>=4.5.0 and typing_extensions>=4.1.0 - #1325
Open
Linxiushen wants to merge 1 commit into
Open
Linxiushen wants to merge 1 commit into
Linxiushen wants to merge 1 commit into
Conversation
pyproject.toml declares anyio>=4.0.0 and typing_extensions>=4.0.0, but the SDK calls APIs that neither of those releases provides: - subprocess_cli.py passes `user=self._options.user` to anyio.open_process() on every connect(). anyio added the user/group/umask keywords in 4.5.0; on 4.0.0-4.4.0 the TypeError is swallowed by the generic `except Exception` in connect() and surfaces as `CLIConnectionError: Failed to start Claude Code: open_process() got an unexpected keyword argument 'user'`. - __init__.py imports `is_typeddict` from typing_extensions on Python 3.10. typing_extensions added it in 4.1.0; on 4.0.0/4.0.1 `import claude_agent_sdk` fails with ImportError. Nothing catches this today because mcp>=1.23.0 transitively requires anyio>=4.5 and typing-extensions>=4.9.0, so an ordinary resolve never selects the declared floors. Installs that do honour them -- --no-deps, constraints files, lockfiles pinned to the minimum, redistributors packaging the declared range -- get an SDK that cannot start the CLI or cannot be imported. Raise each floor to the release that actually provides the API, and add tests/test_dependency_floors.py so a floor cannot drift below its call site again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Linxiushen
force-pushed
the
fix/declared-dependency-floors-are-below-the
branch
from
September 28, 2026 03:08
14b99fe to
2a86262
Compare
sigley
approved these changes
Oct 7, 2026
sigley
left a comment
There was a problem hiding this comment.
Validated exact head 2a86262 and the advertised lower bounds independently. In the upstream wheels, AnyIO 4.4.0 lacks open_process user/group/umask while 4.5.0 has them; typing_extensions 4.0.1 does not export is_typeddict while 4.1.0 does. The branch merges cleanly with current main, its dependency-floor regression passes 4/4, and compile/diff checks are clean. One non-blocking integration note: if #1341 lands first, a rebase should preserve that PR's unconditional typing_extensions dependency rather than restoring the Python <3.11 marker.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two declared lower bounds in
pyproject.tomladvertise support for dependency versions the SDK cannot actually run on. This raises each floor to the first release that provides the API the SDK calls, and adds a test so the bounds cannot drift below their call sites again.anyio>=4.0.0->anyio>=4.5.0(and the matchinganyio[trio]in thedevextra)typing_extensions>=4.0.0->typing_extensions>=4.1.0Same class of fix as #891 (
fix(deps): require mcp>=1.19.0 for in-process SDK MCP tools).Root cause
anyio>=4.0.0.subprocess_cli.py:866passesuser=self._options.usertoanyio.open_process()unconditionally. Theuser/group/extra_groups/umaskkeywords landed in anyio 4.5.0; before thatopen_process()accepts onlycommand, stdin, stdout, stderr, cwd, env, start_new_session. On anyio 4.0-4.4 everyconnect()raises, and theexcept Exceptionat line 898 rewrites it into a misleading error:typing_extensions>=4.0.0; python_version<'3.11'.__init__.py:26doesfrom typing_extensions import is_typeddicton Python 3.10.is_typeddictwas added in typing_extensions 4.1.0; on 4.0.x,import claude_agent_sdkfails outright withImportError: cannot import name 'is_typeddict' from 'typing_extensions'.Both floors were set before the SDK adopted those APIs and were never raised alongside them.
Scope, stated plainly
These bounds are wrong, but they are currently masked by transitive constraints, so this is a metadata-correctness fix rather than a live install failure. The declared
mcp>=1.23.0itself requiresanyio>=4.5, and anyio requirestyping-extensions>=4.1onpython_version<'3.11'. Sopip install claude-agent-sdk anyio==4.4.0already fails to resolve onmaintoday.The wrong bounds still bite installs that honor only what the SDK declares:
--no-deps, constraints files and lockfiles pinned to the declared minimum, redistributors that package the declared range, and any future loosening of the mcp floor (mcp<3.0.0is allowed). Relying on a dependency-of-a-dependency's bound to hold our own requirement is also just fragile.Fix
Raise the two floors to the first releases that provide the APIs actually called. Both are old releases (anyio 4.5.0 is from Sept 2024, typing_extensions 4.1.0 from Feb 2022), so no realistic environment is excluded.
tests/test_dependency_floors.pyguards the result. Nothing else can: resolvers install the newest version satisfying every requirement, so CI always runs on the latest release and never exercises a declared floor. The test asserts each declared>=bound is at least the version providing the API, and two companion tests keep the justifications honest -- one confirmssubprocess_cli.pystill passesuser=toopen_processand that the installed anyio accepts every keyword the call site uses, the other that__init__.pystill importsis_typeddictfromtyping_extensions. Delete an entry when its call site goes away.Testing
Ran in the checkout's existing
.venv(Python 3.12.14, pytest 9.1.1, anyio 4.15.1, typing_extensions 4.16.0).pytest tests/test_dependency_floors.py -v-> 4 passed.pytest tests/-> 1591 passed, 6 skipped (rebased onmainat 36f9548).pytest tests/test_transport.py tests/test_types.py tests/test_build_wheel.py-> 286 passed, 1 skipped.ruff check tests/test_dependency_floors.py pyproject.toml-> all checks passed;ruff format --check tests/test_dependency_floors.py-> already formatted.mypy src/ scripts/(the repo'slint.ymlscope) -> success, 33 files.mypy tests/test_dependency_floors.py-> success (tests are outside the repo's mypy scope; checked anyway).Regression check, both directions. Restoring
pyproject.tomlfromorigin/mainturns the new test red -- 2 failed, 2 passed, naming the offending requirement and the missing API:With the fix in place it passes again. The test genuinely fails without the change.
Floors established from the upstream wheels, not from changelog prose. Downloaded the anyio 4.0.0 / 4.4.0 / 4.5.0 wheels from PyPI and AST-parsed
anyio/_core/_subprocesses.py:open_processhas nouserparameter in 4.0.0 or 4.4.0 and gains it in 4.5.0.is_typeddictis absent from typing_extensions 4.0.0 and 4.0.1 and present in 4.1.0.Both failures reproduced live:
anyio==4.4.0TypeError: open_process() got an unexpected keyword argument 'user'anyio==4.15.1typing_extensions==4.0.1(py3.10)ImportError: cannot import name 'is_typeddict'typing_extensions==4.16.0(py3.10)End-to-end through the real transport: fresh py3.12 venv,
pip install -e ., thenpip install --no-deps anyio==4.4.0, thenSubprocessCLITransport(...).connect()->CLIConnectionError: Failed to start Claude Code: open_process() got an unexpected keyword argument 'user'.Remaining floors checked for the same defect -- nothing else needs raising here: the SDK only uses
sniffio.current_async_library(present since 1.0) andjsonschema.validate/jsonschema.ValidationError(both long predate the 4.20.0 floor).🤖 Generated with Claude Code