Add typed hook input structures (PreToolUseHookInput,
PostToolUseHookInput, etc.) to provide better IDE autocomplete and type
safety for hook callbacks. Also convert HookContext from dataclass to
TypedDict to match runtime behavior.
Changes:
- Add BaseHookInput, PreToolUseHookInput, PostToolUseHookInput,
UserPromptSubmitHookInput, StopHookInput, SubagentStopHookInput, and
PreCompactHookInput TypedDict classes
- Update HookCallback signature to use HookInput union type
- Convert HookContext from dataclass to TypedDict (fixes type mismatch)
- Export all new hook input types from __init__.py
- Update all examples and tests to use typed hook inputs
Benefits:
- Zero breaking changes (TypedDict is dict-compatible at runtime)
- Full type safety and IDE autocomplete for hook callbacks
- Matches TypeScript SDK structure exactly
- Self-documenting hook input fields
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Fixes critical bug where hook outputs using `async_` and `continue_`
(Python-safe names avoiding keyword conflicts) were not being converted
to `async` and `continue` as expected by the CLI. This caused hook
control fields like `{"decision": "block"}` or `{"continue_": False}` to
be silently ignored.
Changes:
- Add _convert_hook_output_for_cli() to handle field name conversion
- Apply conversion in hook callback handling
- Update type documentation to clarify field name usage
- Add comprehensive test coverage for field name conversion
- Update existing tests to verify conversion occurs correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.1.2 after publishing to PyPI.
## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_agent_sdk/_version.py`
- Updated `CHANGELOG.md` with release notes
## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.2/
- Install with: `pip install claude-agent-sdk==0.1.2`
🤖 Generated by GitHub Actions
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Closes the gap between Python and TypeScript SDK hook output types by
adding:
- `reason` field for explaining decisions
- `continue_` field for controlling execution flow
- `suppressOutput` field for hiding stdout
- `stopReason` field for stop explanations
- `decision` now supports both "approve" and "block" (not just "block")
- `AsyncHookJSONOutput` type for deferred hook execution
- Proper typing for `hookSpecificOutput` with discriminated unions
Also adds comprehensive examples and tests:
- New examples in hooks.py demonstrating all new fields
- Unit tests in test_tool_callbacks.py for new output types
- E2E tests in e2e-tests/test_hooks.py with real API calls
- CI integration in .github/workflows/test.yml
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Temporarily removes windows-latest from the test-e2e job matrix to
disable Windows end-to-end testing. Unit tests continue to run on
Windows.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
https://github.com/anthropics/claude-agent-sdk-python/issues/227
Fixes zod issues returned by cli subprocess transport - example now runs
correctly to completion:
```
============================================================
Tool Permission Callback Example
============================================================
This example demonstrates how to:
1. Allow/deny tools based on type
2. Modify tool inputs for safety
3. Log tool usage
4. Prompt for unknown tools
============================================================
📝 Sending query to Claude...
📨 Receiving response...
💬 Claude: I'll help you with these tasks. Let me execute them in sequence.
💬 Claude: Now I'll create a simple Python hello world script:
🔧 Tool Permission Request: Write
Input: {
"file_path": "/Users/vimota/code/claude-agent-sdk-python/hello.py",
"content": "#!/usr/bin/env python3\n\nprint(\"Hello, World!\")\n"
}
⚠️ Redirecting write from /Users/vimota/code/claude-agent-sdk-python/hello.py to ./safe_output/hello.py
💬 Claude: Now let's run the script:
🔧 Tool Permission Request: Bash
Input: {
"command": "python hello.py",
"description": "Run hello.py script"
}
✅ Allowing bash command: python hello.py
💬 Claude: Let me check where the file was created:
💬 Claude: I see the file was created in the `safe_output` directory. Let me run it from there:
🔧 Tool Permission Request: Bash
Input: {
"command": "python ./safe_output/hello.py",
"description": "Run hello.py from safe_output"
}
✅ Allowing bash command: python ./safe_output/hello.py
💬 Claude: Perfect! All tasks completed successfully:
1. **Listed files** - The directory contains a Python SDK project with source code in `src/`, tests, examples, and configuration files.
2. **Created hello.py** - A simple Python script was created at `./safe_output/hello.py` with a basic "Hello, World!" print statement.
3. **Ran the script** - The script executed successfully and printed "Hello, World!" to the console.
Note: The file was created in the `safe_output/` subdirectory rather than the root directory.
✅ Task completed!
Duration: 31158ms
Cost: $0.0736
Messages processed: 18
============================================================
Tool Usage Summary
============================================================
1. Tool: Write
Input: {
"file_path": "/Users/vimota/code/claude-agent-sdk-python/hello.py",
"content": "#!/usr/bin/env python3\n\nprint(\"Hello, World!\")\n"
}
Suggestions: [{'type': 'setMode', 'mode': 'acceptEdits', 'destination': 'session'}]
2. Tool: Bash
Input: {
"command": "python hello.py",
"description": "Run hello.py script"
}
Suggestions: [{'type': 'addRules', 'rules': [{'toolName': 'Bash', 'ruleContent': 'python:*'}], 'behavior': 'allow', 'destination': 'localSettings'}]
3. Tool: Bash
Input: {
"command": "python ./safe_output/hello.py",
"description": "Run hello.py from safe_output"
}
Suggestions: [{'type': 'addRules', 'rules': [{'toolName': 'Bash', 'ruleContent': 'python:*'}], 'behavior': 'allow', 'destination': 'localSettings'}]
```
Replace GitHub API-based commits with local git workflow and integrate
claude-code-action to automatically generate changelog entries. The
workflow now:
- Creates release branch locally with version commits
- Uses Claude to review changes and update CHANGELOG.md
- Pushes complete branch with all commits together
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Document the features and improvements released in version 0.1.1,
including minimum Claude Code version check, updated PermissionResult
types, and simplified model references.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Add cross-platform testing for Windows alongside Linux across all test
jobs (unit tests, e2e tests, and examples). Uses native Windows
installation via PowerShell script and platform-specific timeout
handling for example scripts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Make test_cli_path_accepts_pathlib_path platform-aware by comparing
with str(path)
- Fix test_build_command_with_add_dirs to check directories individually
- Fix test_build_command_with_mcp_servers_as_file_path to handle Path
conversion
- Fix test_query_with_async_iterable to properly execute Python scripts
on Windows
All tests now pass on both Windows and Unix-like systems (110/110 tests
passing).
Fixes the issue #217 where pathlib.Path automatically converts path
separators based on the operating system, causing test assertions to
fail on Windows.
This PR updates the version to 0.1.1 after publishing to PyPI.
## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_agent_sdk/_version.py`
## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.1/
- Install with: `pip install claude-agent-sdk==0.1.1`
🤖 Generated by GitHub Actions
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Add version check in subprocess transport that runs `claude -v` on
connect
- Display warning to stderr if version is below 2.0.0
- Update README prerequisites to specify Claude Code 2.0.0+
- Version check is non-blocking (warns but continues execution)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
Replace all dated model references (claude-sonnet-4-20250514,
claude-3-5-sonnet-20241022) with the simplified claude-sonnet-4-5
identifier across examples, tests, and documentation.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
`query` supports custom transport, but ClaudeSDKClient does not. So I
add this feature. eg.
```python
from claude_code_sdk import ClaudeSDKClient, Transport
class MyCustomTransport(Transport):
# Implement custom transport logic
pass
transport = MyCustomTransport()
async with ClaudeSDKClient( transport=transport) as client:
await client.query("Greet Alice")
# Extract and print response
async for msg in client.receive_response():
print(msg)
```
This PR updates the version to 0.1.0 after publishing to PyPI.
## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_agent_sdk/_version.py`
## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.0/
- Install with: `pip install claude-agent-sdk==0.1.0`
🤖 Generated by GitHub Actions
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Summary
- Adds `set_permission_mode()` method to dynamically change permission
modes during streaming sessions
- Adds `set_model()` method to switch AI models mid-conversation
- Implements control protocol support matching the TypeScript SDK's
capabilities
## Motivation
The TypeScript SDK supports dynamic control through
`setPermissionMode()` and `setModel()` methods on the Query interface.
This PR brings the same functionality to the Python SDK, allowing users
to:
1. Start with restrictive permissions for code review, then switch to
`acceptEdits` for implementation
2. Use different models for different parts of a task (e.g., Sonnet for
complex analysis, Haiku for simple tasks)
3. Adjust permissions based on workflow needs without restarting
sessions
## Changes
- **ClaudeSDKClient**: Added `set_permission_mode(mode)` and
`set_model(model)` methods
- **Internal Query class**: Added `set_model(model)` method to send
control requests
- **E2E tests**: Added comprehensive tests verifying the functionality
works with real API calls
## Test Plan
- [x] All existing unit tests pass (102 tests)
- [x] New E2E tests added and passing:
- `test_set_permission_mode`: Verifies permission mode changes take
effect
- `test_set_model`: Confirms model switching works mid-conversation
- `test_interrupt`: Validates interrupt capability
- [x] Type checking passes (`mypy`)
- [x] Linting passes (`ruff`)
## Usage Example
```python
async with ClaudeSDKClient() as client:
# Start with default permissions for review
await client.query("Analyze this code for issues")
# Switch to auto-accept edits for implementation
await client.set_permission_mode('acceptEdits')
await client.query("Now fix the issues we found")
# Use a different model for simpler tasks
await client.set_model('claude-3-5-haiku-20241022')
await client.query("Add a simple docstring")
```
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
Replace separate system_prompt and append_system_prompt fields with a
single system_prompt field that accepts:
- string: custom system prompt
- {"preset": "claude_code"}: use default Claude Code prompt
- {"preset": "claude_code", "append": "..."}: default prompt with
additions
- None/undefined: vanilla Claude with no system prompt
This matches the TypeScript SDK API design and provides more flexible
system prompt configuration.
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Add support for custom agent definitions via `agents` option
- Add support for controlling setting sources via `setting_sources`
option
- Add `/commit` slash command to project
- Add examples demonstrating both features
- Add e2e tests for verification
## Changes
### Core Implementation
- Add `AgentDefinition` and `SettingSource` types to `types.py`
- Add `agents` and `setting_sources` fields to `ClaudeCodeOptions`
- Update subprocess CLI transport to pass `--agents` and
`--setting-sources` flags
- **Default behavior**: When `setting_sources` is not provided, pass
empty string (no settings loaded)
- Handle empty `setting_sources` array correctly (pass empty string to
CLI)
### Examples
- `examples/agents.py`: Demonstrates custom agent definitions with
different tools and models
- `examples/setting_sources.py`: Shows how setting sources control which
settings are loaded
- Default behavior (no settings)
- User-only settings
- User + project settings
### Tests
- Add e2e tests verifying agents and setting_sources functionality
- Test default behavior (no settings loaded)
- Test filtering by setting source
- Use `output_style` checking to verify settings loaded/not loaded
- Tests use temporary directories for isolated testing
### Project Config
- Add `.claude/commands/commit.md` slash command for git commits
## Test Plan
- [x] E2E tests added for all new functionality
- [ ] CI tests pass
- [ ] Examples run successfully
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Include the SDK version in the environment when spawning the Claude CLI
process.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
Add support for fork_session parameter which allows resumed sessions to
fork to a new session ID rather than continuing the previous session.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
Use `gh release create --generate-notes` to automatically include PR
titles, commits, and contributors in release notes instead of static
template text.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.0.23 after publishing to PyPI.
## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_code_sdk/__init__.py`
## Release Information
- Published to PyPI: https://pypi.org/project/claude-code-sdk/0.0.23/
- Install with: `pip install claude-code-sdk==0.0.23`
🤖 Generated by GitHub Actions
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Summary
- Adds support for streaming partial messages through the
`include_partial_messages` option
- Introduces `StreamEvent` message type to handle Anthropic API stream
events
- Enables real-time streaming of Claude's responses for building
interactive UIs
## Changes
- Added `StreamEvent` dataclass with proper structure matching
TypeScript SDK (uuid, session_id, event, parent_tool_use_id)
- Added `include_partial_messages` boolean option to `ClaudeCodeOptions`
- Updated message parser to handle `stream_event` message type
- Updated subprocess CLI transport to pass `--include-partial-messages`
flag when enabled
- Added example demonstrating partial message streaming usage
## Test plan
- [x] Verified CLI flag is passed correctly when
`include_partial_messages=True`
- [x] Confirmed `StreamEvent` structure matches TypeScript SDK
implementation
- [x] Added test for user parameter in transport
- [x] Example runs successfully with streaming events
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.0.22 after publishing to PyPI.
## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_code_sdk/__init__.py`
## Release Information
- Published to PyPI: https://pypi.org/project/claude-code-sdk/0.0.22/
- Install with: `pip install claude-code-sdk==0.0.22`
🤖 Generated by GitHub Actions
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Summary
This PR adds support for custom tool callbacks and comprehensive e2e
testing for MCP calculator functionality.
## Key Features Added
- **Custom tool permission callbacks** - Allow dynamic tool permission
control via `can_use_tool` callback
- **E2E test suite** - Real Claude API tests validating MCP tool
execution end-to-end
- **Fixed MCP calculator example** - Now properly uses `allowed_tools`
for permission management
## Changes
### Custom Callbacks
- Added `ToolPermissionContext` and `PermissionResult` types for tool
permission handling
- Implemented `can_use_tool` callback support in SDK client
- Added comprehensive tests in `tests/test_tool_callbacks.py`
### E2E Testing Infrastructure
- Created `e2e-tests/` directory with pytest-based test suite
- `test_mcp_calculator.py` - Tests all calculator operations with real
API calls
- `conftest.py` - Pytest config with mandatory API key validation
- GitHub Actions workflow for automated e2e testing on main branch
- Comprehensive documentation in `e2e-tests/README.md`
### Bug Fixes
- Fixed MCP calculator example to use `allowed_tools` instead of
incorrect `permission_mode`
- Resolved tool permission issues preventing MCP tools from executing
## Testing
E2E tests require `ANTHROPIC_API_KEY` environment variable and will fail
without it.
Run locally:
```bash
export ANTHROPIC_API_KEY=your-key
python -m pytest e2e-tests/ -v -m e2e
```
Run unit tests including callback tests:
```bash
python -m pytest tests/test_tool_callbacks.py -v
```
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kashyap Murali <kashyap@anthropic.com>
Configure automatic formatting via ruff check --fix and ruff format
after Edit/Write/MultiEdit operations
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Standardized the trigger configuration across test, lint, and e2e
workflows
- All workflows now use consistent format with pull_request listed
before push
- E2E workflow now also runs on pushes to main branch
## Test plan
- [ ] Verify workflows trigger correctly on pull requests
- [ ] Verify workflows trigger correctly on pushes to main branch
- [ ] Check that e2e tests run successfully on main branch
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Added GitHub Actions workflow to test SDK integration with Claude Code
CLI
- Tests run on PR open/synchronize events across Python 3.10-3.13
## Test plan
- [x] Workflow triggers on PR events
- [x] Installs Claude Code CLI via official script
- [x] Runs quickstart example
- [x] Runs streaming_mode example
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
This PR updates the version to 0.0.21 after publishing to PyPI.
## Changes
- Updated version in `pyproject.toml`
- Updated version in `src/claude_code_sdk/__init__.py`
## Release Information
- Published to PyPI: https://pypi.org/project/claude-code-sdk/0.0.21/
- Install with: `pip install claude-code-sdk==0.0.21`
🤖 Generated by GitHub Actions
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Hide hooks, tool permission callbacks, and SDK MCP server APIs from
public interface while keeping implementation code intact. These
features are not yet stable and should not be documented or exposed to
users.
Changes:
- Remove hook-related exports (HookCallback, HookContext, HookMatcher)
from __all__
- Remove tool permission exports (CanUseTool, ToolPermissionContext)
from __all__
- Remove SDK MCP exports (tool, create_sdk_mcp_server, SdkMcpTool) from
__all__
- Delete examples using unstable APIs (tool_permission_callback.py,
mcp_calculator.py)
- Remove SDK MCP server documentation from README
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Add conditional stderr routing in subprocess transport
- When debug-to-stderr flag is set, Claude CLI debug output goes
directly to Python's stderr
- Keeps stdout clean for JSON message parsing while providing debug
visibility
- Simplifies implementation by removing temp file and background task
complexity
- Update examples to demonstrate debug-to-stderr usage
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
This PR addresses the `anyio.BrokenResourceError` issue from #139 and
aligns the Python SDK's error handling with the TypeScript SDK
implementation.
## Changes
### Fix stream closure issue in Query
- Removed the `async with` context manager from
`Query.receive_messages()` that was closing the stream after first use
- The stream now remains open for the entire session, allowing multiple
queries in streaming mode
- This fixes the `BrokenResourceError` that occurred during multi-turn
conversations
### Align subprocess transport with TypeScript SDK
Following the TypeScript `ProcessTransport` implementation pattern:
- **Added `_exit_error` tracking**: Captures and preserves process-level
errors for better error propagation
- **Enhanced `write()` method checks**:
- Validates transport readiness before writing
- Checks if process is still alive (exit code)
- Checks for stored exit errors before attempting writes
- Marks transport as not ready on write failures
- **Improved error handling in `connect()`**: Stores errors as
`_exit_error` for later reference
- **Simplified `is_ready()` method**: Now just returns the `_ready`
flag, matching TypeScript's simpler approach
### Other improvements
- Added asyncio pytest plugin configuration (`-p asyncio` in
pyproject.toml)
- Added clarifying comment about TextReceiveStream line handling
## Testing
The multi-turn conversation example now works correctly:
```bash
python examples/streaming_mode.py multi_turn_conversation
```
## Related Issues
Fixes#139
## Summary
Adds comprehensive support for tool permission callbacks and hook
callbacks to the Python SDK, enabling fine-grained control over tool
execution and custom event handling.
## Key Changes
- **Tool Permission Callbacks**: Control which tools Claude can use and
modify their inputs
- type with async support
- with suggestions from CLI
- for structured responses
- **Hook Callbacks**: React to events in the Claude workflow
- type for event handlers
- for conditional hook execution
- Support for tool_use_start, tool_use_end events
- **Integration**: Full plumbing through ClaudeCodeOptions → Client →
Query
- **Examples**: Comprehensive example showing permission control
patterns
- **Tests**: Coverage for all callback scenarios
## Implementation Details
- Callbacks are registered during initialization phase
- Control protocol handles can_use_tool and hook_callback requests
- Backwards compatible with dict returns for tool permissions
- Proper error handling and type safety throughout
Builds on top of #139's control protocol implementation.
---------
Co-authored-by: Dickson Tsai <dickson@anthropic.com>
## Summary
Adds in-process SDK MCP server support to the Python SDK, building on
the control protocol from #139.
**Note: Targets `dickson/control` branch (PR #139), not `main`.**
## Key Changes
- Added `@tool` decorator and `create_sdk_mcp_server()` API for defining
in-process MCP servers
- SDK MCP servers run directly in the Python process (no subprocess
overhead)
- Moved SDK MCP handling from Transport to Query class for proper
architectural layering
- Added `McpSdkServerConfig` type and integrated with control protocol
## Example
```python
from claude_code_sdk import tool, create_sdk_mcp_server
@tool("greet", "Greet a user", {"name": str})
async def greet_user(args):
return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
server = create_sdk_mcp_server(name="my-tools", tools=[greet_user])
options = ClaudeCodeOptions(mcp_servers={"tools": server})
```
## Testing
- Added integration tests in `test_sdk_mcp_integration.py`
- Added example calculator server in `examples/mcp_calculator.py`
---------
Co-authored-by: Dickson Tsai <dickson@anthropic.com>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
This PR implements control protocol support in the Python SDK, aligning
it with the TypeScript implementation pattern. The refactor introduces a
Query + Transport separation to enable bidirectional communication
between the SDK and CLI.
## Motivation
The previous Python SDK implementation used a high-level abstraction in
the Transport ABC (`send_request`/`receive_messages`) that couldn't
handle bidirectional communication. This prevented support for:
- Control messages from CLI to SDK that need responses
- Hooks implementation
- Dynamic permission mode changes
- SDK MCP servers
## Changes
### Core Architecture Refactor
1. **New Query Class** (`src/claude_code_sdk/_internal/query.py`)
- Manages control protocol on top of Transport
- Handles control request/response routing
- Manages initialization handshake with timeout
- Supports hook callbacks and tool permission callbacks
- Implements message streaming
2. **Refactored Transport ABC**
(`src/claude_code_sdk/_internal/transport/__init__.py`)
- Changed from high-level (`send_request`/`receive_messages`) to
low-level (`write`/`read_messages`) interface
- Now handles raw I/O instead of protocol logic
- Aligns with TypeScript ProcessTransport pattern
3. **Updated SubprocessCLITransport**
(`src/claude_code_sdk/_internal/transport/subprocess_cli.py`)
- Simplified to focus on raw message streaming
- Removed protocol logic (moved to Query)
- Improved cleanup and error handling
4. **Enhanced ClaudeSDKClient** (`src/claude_code_sdk/client.py`)
- Now uses Query for control protocol
- Supports initialization messages
- Better error handling for control protocol failures
### Control Protocol Features
- **Initialization handshake**: SDK sends initialize request, CLI
responds with supported commands
- **Control message types**:
- `initialize`: Establish bidirectional connection
- `interrupt`: Cancel ongoing operations
- `set_permission_mode`: Change permission mode dynamically
- **Timeout handling**: 60-second timeout for initialization to handle
CLI versions without control support
### Examples
Updated `examples/streaming_mode.py` to demonstrate control protocol
initialization and error handling.
## Testing
- Tested with current CLI (no control protocol support yet) - gracefully
falls back
- Verified backward compatibility with existing `query()` function
- Tested initialization timeout handling
- Verified proper cleanup on errors
## Design Alignment
This implementation closely follows the TypeScript reference:
- `src/core/Query.ts` → `src/claude_code_sdk/_internal/query.py`
- `src/transport/ProcessTransport.ts` →
`src/claude_code_sdk/_internal/transport/subprocess_cli.py`
- `src/entrypoints/sdk.ts` → `src/claude_code_sdk/client.py`
## Next Steps
Once the CLI implements the control protocol handler, this will enable:
- Hooks support
- Dynamic permission mode changes
- SDK MCP servers
- Improved error recovery
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kashyap Murali <kashyap@anthropic.com>
The subprocess transport was ignoring user-specified cwd from
ClaudeCodeOptions, causing the working directory preference to be
overridden by os.environ. This fix ensures the CWD environment variable
is properly passed to child processes.
- Add PWD env var when self._cwd is set in SubprocessCLITransport
- Fixes issue where custom working directory was not respected
- Maintains backward compatibility
Tests: All existing tests pass
The PR should address the issue where user prompt maybe treated as a
option with leading `-`
Assuming `claude` does follow POSIX standard, the `--` option should
solve the issue.
fixes#129