Understanding when to use query() for one-shot queries versus ClaudeSDKClient for interactive conversations
The Claude Agent SDK provides two main ways to interact with Claude: the query() function for simple one-shot queries and ClaudeSDKClient for interactive, stateful conversations.
Perfect for simple questions where you know the entire input upfront.
async for message in query(prompt="What is 2+2?"): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(block.text)
Batch Processing
Ideal for processing multiple independent prompts.
prompts = ["Explain async/await", "What is a closure?", "Define recursion"]for prompt in prompts: async for message in query(prompt=prompt): # Process each response pass
CI/CD Pipelines
Great for automated scripts where all inputs are known.
async for message in query( prompt="Review this pull request for security issues", options=ClaudeAgentOptions( cwd="/path/to/repo", permission_mode="default" )): # Process review results pass
Code Generation
Useful for one-shot code generation tasks.
async for message in query( prompt="Create a Python web server with FastAPI", options=ClaudeAgentOptions( system_prompt="You are an expert Python developer", cwd="/home/user/project" )): # Process generated code pass
async with ClaudeSDKClient() as client: # Send initial query await client.query("Help me debug this code") async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): display_to_user(msg) # User can send follow-ups await client.query("What about the edge case?") async for msg in client.receive_response(): display_to_user(msg)
Interactive Debugging
When you need to react to Claude’s responses and ask follow-up questions.
async with ClaudeSDKClient() as client: await client.query("Analyze this error") async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): # Analyze response and ask follow-up if "needs more context" in extract_text(msg): await client.query("Here's the stack trace...") break
Multi-turn Conversations
When you need to maintain context across multiple exchanges.
async with ClaudeSDKClient() as client: await client.query("Let's refactor this module") async for msg in client.receive_response(): pass # Process initial response await client.query("Now add error handling") async for msg in client.receive_response(): pass # Claude remembers the refactoring context
Dynamic Control
When you need to change settings or interrupt during execution.
async with ClaudeSDKClient() as client: await client.query("Review this codebase") # Start listening for messages async for msg in client.receive_messages(): if user_approves(): # Switch permission mode dynamically await client.set_permission_mode('acceptEdits') await client.query("Implement the changes") elif user_cancels(): # Interrupt the current operation await client.interrupt() break
from claude_agent_sdk import query, ClaudeAgentOptionsasync def generate_code(): """Generate a Python web server.""" async for message in query( prompt="Create a FastAPI server with health check endpoint", options=ClaudeAgentOptions( system_prompt="You are an expert Python developer", cwd="/home/user/project", permission_mode="acceptEdits" ) ): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(block.text) elif isinstance(message, ResultMessage): print(f"Cost: ${message.total_cost_usd:.4f}")
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptionsasync def interactive_chat(): """Interactive debugging session.""" options = ClaudeAgentOptions( cwd="/home/user/project", permission_mode="default" ) async with ClaudeSDKClient(options) as client: # Initial query await client.query("Help me understand this error") async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}") # Follow-up based on response await client.query("Can you show me how to fix it?") async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): # User reviews the solution if should_accept(): await client.set_permission_mode('acceptEdits') await client.query("Please implement the fix") elif isinstance(msg, ResultMessage): print(f"Session complete. Cost: ${msg.total_cost_usd:.4f}")
Important Limitation: As of v0.0.20, ClaudeSDKClient instances cannot be used across different async runtime contexts (e.g., different trio nurseries or asyncio task groups). The client must complete all operations within the same async context where it was connected.