Skip to content

bug: #380 fix only works with async generator prompts, not string promptsย #386

@murtaza64

Description

@murtaza64

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 = 1

Fails (string prompt):

async for message in query(prompt="Please greet Alice", options=options):
    print(f"Message: {type(message).__name__}")
# Crashes with CLIConnectionError

Error

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 args

When 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:

  1. In _internal/client.py, wait for _first_result_event before exiting the finally block when sdk_mcp_servers is present
  2. 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):
    ...

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions