Skip to content

fix(deps): require anyio>=4.5.0 and typing_extensions>=4.1.0 - #1325

Open
Linxiushen wants to merge 1 commit into
anthropics:mainfrom
Linxiushen:fix/declared-dependency-floors-are-below-the
Open

Linxiushen wants to merge 1 commit into
anthropics:mainfrom
Linxiushen:fix/declared-dependency-floors-are-below-the

Conversation

@Linxiushen

Copy link
Copy Markdown

Summary

Two declared lower bounds in pyproject.toml advertise 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 matching anyio[trio] in the dev extra)
  • typing_extensions>=4.0.0 -> typing_extensions>=4.1.0

Same 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:866 passes user=self._options.user to anyio.open_process() unconditionally. The user/group/extra_groups/umask keywords landed in anyio 4.5.0; before that open_process() accepts only command, stdin, stdout, stderr, cwd, env, start_new_session. On anyio 4.0-4.4 every connect() raises, and the except Exception at line 898 rewrites it into a misleading error:

CLIConnectionError: Failed to start Claude Code: open_process() got an unexpected keyword argument 'user'

typing_extensions>=4.0.0; python_version<'3.11'. __init__.py:26 does from typing_extensions import is_typeddict on Python 3.10. is_typeddict was added in typing_extensions 4.1.0; on 4.0.x, import claude_agent_sdk fails outright with ImportError: 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.0 itself requires anyio>=4.5, and anyio requires typing-extensions>=4.1 on python_version<'3.11'. So pip install claude-agent-sdk anyio==4.4.0 already fails to resolve on main today.

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.0 is 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.py guards 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 confirms subprocess_cli.py still passes user= to open_process and that the installed anyio accepts every keyword the call site uses, the other that __init__.py still imports is_typeddict from typing_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 on main at 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's lint.yml scope) -> 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.toml from origin/main turns the new test red -- 2 failed, 2 passed, naming the offending requirement and the missing API:

'anyio>=4.0.0' admits anyio 4.0.0, which lacks anyio.open_process(user=...)
"typing_extensions>=4.0.0; python_version<'3.11'" admits typing-extensions 4.0.0, which lacks typing_extensions.is_typeddict

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_process has no user parameter in 4.0.0 or 4.4.0 and gains it in 4.5.0. is_typeddict is absent from typing_extensions 4.0.0 and 4.0.1 and present in 4.1.0.

Both failures reproduced live:

pinned version result
anyio==4.4.0 TypeError: open_process() got an unexpected keyword argument 'user'
anyio==4.15.1 works
typing_extensions==4.0.1 (py3.10) ImportError: cannot import name 'is_typeddict'
typing_extensions==4.16.0 (py3.10) works

End-to-end through the real transport: fresh py3.12 venv, pip install -e ., then pip install --no-deps anyio==4.4.0, then SubprocessCLITransport(...).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) and jsonschema.validate / jsonschema.ValidationError (both long predate the 4.20.0 floor).

🤖 Generated with Claude Code

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
Linxiushen force-pushed the fix/declared-dependency-floors-are-below-the branch from 14b99fe to 2a86262 Compare September 28, 2026 03:08

@sigley sigley left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

No deployments
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