Bug: ClaudeSDKClient Cannot Be Shared Across Async Runtime Contexts
Affected version: v0.0.20+
File: src/claude_agent_sdk/client.py, line 55
Description
ClaudeSDKClient instances are currently tied to the async context in which they were connected. Once connect() is called, the client spawns a persistent internal anyio task group responsible for reading incoming messages. This task group lives for the entire lifetime of the connection — from connect() through disconnect() — and cannot be safely accessed or reused from a different async runtime context.
This means the client cannot be passed to, or reused across:
- Different
trio nurseries
- Different
asyncio task groups
- Any other concurrent async scope that was not the original caller of
connect()
Why This Is a Problem
This constraint makes it difficult to build real-world applications where a single connected client needs to be shared or handed off between independent async workers, background tasks, or structured concurrency scopes. Common use cases that break under this limitation include:
- Connecting once at startup and reusing the client in multiple task groups
- Delegating work to a worker pool where individual tasks weren't the ones that called
connect()
- Using the client inside frameworks that manage their own async lifecycles (e.g., FastAPI, Starlette, or Hypercorn with Trio)
Expected Behavior
A connected ClaudeSDKClient instance should be usable from any async context, not just the one in which it was originally connected. The internal message-reading task group should either:
- Be scoped independently of any caller-owned nursery/task group, or
- Support safe cross-context handoff via a well-defined mechanism
Suggested Fix
Consider running the internal anyio task group in a dedicated background thread or a standalone top-level task scope that is not a child of the caller's nursery. This would decouple the client's lifetime from the async scope of the connecting caller.
References
Bug:
ClaudeSDKClientCannot Be Shared Across Async Runtime ContextsAffected version: v0.0.20+
File:
src/claude_agent_sdk/client.py, line 55Description
ClaudeSDKClientinstances are currently tied to the async context in which they were connected. Onceconnect()is called, the client spawns a persistent internalanyiotask group responsible for reading incoming messages. This task group lives for the entire lifetime of the connection — fromconnect()throughdisconnect()— and cannot be safely accessed or reused from a different async runtime context.This means the client cannot be passed to, or reused across:
trionurseriesasynciotask groupsconnect()Why This Is a Problem
This constraint makes it difficult to build real-world applications where a single connected client needs to be shared or handed off between independent async workers, background tasks, or structured concurrency scopes. Common use cases that break under this limitation include:
connect()Expected Behavior
A connected
ClaudeSDKClientinstance should be usable from any async context, not just the one in which it was originally connected. The internal message-reading task group should either:Suggested Fix
Consider running the internal
anyiotask group in a dedicated background thread or a standalone top-level task scope that is not a child of the caller's nursery. This would decouple the client's lifetime from the async scope of the connecting caller.References
client.pyline 55