-
Notifications
You must be signed in to change notification settings - Fork 510
Description
Summary
The fix in PR #380 resolves the ProcessTransport is not ready for writing error when using async generator prompts, but the bug still occurs when using string prompts with SDK MCP servers.
Reproduction
Works (async generator prompt):
async def generate_prompt():
yield {"type": "user", "message": {"role": "user", "content": "Please greet Alice"}}
async for message in query(prompt=generate_prompt(), options=options):
print(f"Message: {type(message).__name__}")
# Output: Tool executes successfully, call_count = 1Fails (string prompt):
async for message in query(prompt="Please greet Alice", options=options):
print(f"Message: {type(message).__name__}")
# Crashes with CLIConnectionErrorError
claude_agent_sdk._errors.CLIConnectionError: ProcessTransport is not ready for writing
Traceback shows error in _handle_control_request at line 320 when writing MCP response.
Root Cause Analysis
The fix in PR #380 adds wait logic in stream_input() (query.py lines 551-564):
if self.sdk_mcp_servers or has_hooks:
await self._first_result_event.wait()However, stream_input() is only called when the prompt is an AsyncIterable.
From _internal/client.py lines 112-117:
# Stream input if it's an AsyncIterable
if isinstance(prompt, AsyncIterable) and query._tg:
query._tg.start_soon(query.stream_input, prompt)
# For string prompts, the prompt is already passed via CLI argsWhen a string prompt is used, stream_input() is never invoked, so there's no wait for the first result. The transport closes while MCP control requests are still in flight.
Suggested Fix
Add similar wait logic for string prompts. Options:
- In
_internal/client.py, wait for_first_result_eventbefore exiting thefinallyblock whensdk_mcp_serversis present - In
query.close(), check for pending MCP requests before closing transport
Environment
- claude-agent-sdk: 0.1.11
- Python: 3.13.5
- Platform: macOS Darwin 24.5.0
Workaround
Convert string prompts to async generators:
async def wrap_prompt(text):
yield {"type": "user", "message": {"role": "user", "content": text}}
async for message in query(prompt=wrap_prompt("My prompt"), options=options):
...