Compare commits

...

32 commits

Author SHA1 Message Date
GitHub Actions
3eb12c5a37 chore: bump bundled CLI version to 2.0.74
Some checks failed
Lint / lint (push) Has been cancelled
Test / test (macos-latest, 3.12) (push) Has been cancelled
Test / test (macos-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (windows-latest, 3.10) (push) Has been cancelled
Test / test (windows-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.12) (push) Has been cancelled
Test / test (windows-latest, 3.13) (push) Has been cancelled
Test / test (macos-latest, 3.10) (push) Has been cancelled
Test / test (macos-latest, 3.11) (push) Has been cancelled
Test / test-examples (3.13) (push) Has been cancelled
Test / test-e2e-docker (push) Has been cancelled
Test / test-e2e (macos-latest, 3.10) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.11) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.12) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.10) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.11) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.12) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.13) (push) Has been cancelled
2025-12-19 22:12:38 +00:00
GitHub Actions
57e8b6ecd5 chore: bump bundled CLI version to 2.0.73
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test-e2e-docker (push) Blocked by required conditions
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test-examples (3.13) (push) Blocked by required conditions
Test / test (macos-latest, 3.10) (push) Waiting to run
2025-12-19 00:16:21 +00:00
Ashwin Bhat
04347495b8
fix: resolve YAML syntax error in create-release-tag workflow (#429)
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e-docker (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Replace heredoc with echo statements to fix YAML parsing issue. The
unindented heredoc content was breaking out of the literal block scalar,
causing `---` to be interpreted as a YAML document separator.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 17:39:12 -08:00
github-actions[bot]
a3df944128
chore: release v0.1.18 (#428)
This PR updates the version to 0.1.18 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.18
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.18
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.18/
- Bundled CLI version: 2.0.72
- Install with: `pip install claude-agent-sdk==0.1.18`

🤖 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>
2025-12-17 17:18:44 -08:00
GitHub Actions
91e65b1927 chore: bump bundled CLI version to 2.0.72
Some checks are pending
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e-docker (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
2025-12-17 21:59:10 +00:00
GitHub Actions
27575ae2ca chore: bump bundled CLI version to 2.0.71
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e-docker (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
2025-12-16 22:09:36 +00:00
Ashwin Bhat
a0ce44a3fa
Add Docker-based test infrastructure for e2e tests (#424)
## Summary

- Add `Dockerfile.test`: Python 3.12 image with Claude Code CLI
installed
- Add `scripts/test-docker.sh`: Local script to run tests in Docker
- Add `test-e2e-docker` job to CI workflow that runs the full e2e suite
in a container
- Add `.dockerignore` to speed up Docker builds

## Context

This helps catch Docker-specific issues like #406 where filesystem-based
agents loaded via `setting_sources=["project"]` may silently fail in
Docker environments.

## Local Usage

```bash
# Run unit tests in Docker (no API key needed)
./scripts/test-docker.sh unit

# Run e2e tests in Docker
ANTHROPIC_API_KEY=sk-... ./scripts/test-docker.sh e2e

# Run all tests
ANTHROPIC_API_KEY=sk-... ./scripts/test-docker.sh all
```

## Test plan

- [x] Unit tests pass in Docker locally (129 passed)
- [ ] CI job runs successfully

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 10:53:13 -08:00
Ashwin Bhat
904c2ec33c
chore: use CHANGELOG.md content for GitHub release notes (#420)
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
Replace auto-generated release notes with content extracted from
CHANGELOG.md for the specific version being released. This provides more
structured and consistent release notes with proper sections like Bug
Fixes, New Features, etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 16:53:23 -08:00
github-actions[bot]
eba5675328
chore: release v0.1.17 (#419)
This PR updates the version to 0.1.17 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.17
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.17
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.17/
- Bundled CLI version: 2.0.70
- Install with: `pip install claude-agent-sdk==0.1.17`

🤖 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>
2025-12-15 16:41:49 -08:00
GitHub Actions
5752f38834 chore: bump bundled CLI version to 2.0.70 2025-12-15 23:52:59 +00:00
Noah Zweben
0ae5c3285c
Add UUID to UserMessage response type to improve devX for rewind (#418)
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
2025-12-15 09:03:58 -08:00
GitHub Actions
f834ba9e15 chore: bump bundled CLI version to 2.0.69
Some checks failed
Lint / lint (push) Has been cancelled
Test / test (macos-latest, 3.10) (push) Has been cancelled
Test / test (macos-latest, 3.11) (push) Has been cancelled
Test / test (macos-latest, 3.12) (push) Has been cancelled
Test / test (macos-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (windows-latest, 3.10) (push) Has been cancelled
Test / test (windows-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.12) (push) Has been cancelled
Test / test (windows-latest, 3.13) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.10) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.11) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.12) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.10) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.11) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.12) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.13) (push) Has been cancelled
Test / test-examples (3.13) (push) Has been cancelled
2025-12-13 01:00:44 +00:00
github-actions[bot]
a1c338726f
chore: release v0.1.16 (#412)
This PR updates the version to 0.1.16 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.16
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.16
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.16/
- Bundled CLI version: 2.0.68
- Install with: `pip install claude-agent-sdk==0.1.16`

🤖 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>
2025-12-12 16:26:36 -08:00
GitHub Actions
d2b3477a4e chore: bump bundled CLI version to 2.0.68 2025-12-12 23:32:48 +00:00
lif
3cbb9e56be
fix: parse error field in AssistantMessage to enable rate limit detection (#405)
Some checks failed
Lint / lint (push) Has been cancelled
Test / test (macos-latest, 3.10) (push) Has been cancelled
Test / test (macos-latest, 3.11) (push) Has been cancelled
Test / test (macos-latest, 3.12) (push) Has been cancelled
Test / test (macos-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (windows-latest, 3.10) (push) Has been cancelled
Test / test (windows-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.12) (push) Has been cancelled
Test / test (windows-latest, 3.13) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.10) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.11) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.12) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.10) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.11) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.12) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.13) (push) Has been cancelled
Test / test-examples (3.13) (push) Has been cancelled
## Summary

Fixes #401

Enables applications to detect API errors (especially `rate_limit`
errors) by properly parsing the `error` field in `AssistantMessage`.

## Problem

The SDK defines `AssistantMessage.error` (including `"rate_limit"`), but
the message parser never extracted this field from the CLI response.
This made it impossible for applications to:
- Detect when rate limits are hit
- Implement retry logic
- Handle other API errors gracefully

## Solution

Added error field extraction in the message parser:

```python
return AssistantMessage(
    content=content_blocks,
    model=data["message"]["model"],
    parent_tool_use_id=data.get("parent_tool_use_id"),
    error=data["message"].get("error"),  # ← Now extracts error field
)
```

## Changes

**Modified: `src/claude_agent_sdk/_internal/message_parser.py`**

The parser now extracts the `error` field from API responses and
populates it in the `AssistantMessage` object.

## Usage Example

Applications can now detect and handle rate limits:

```python
async for message in client.receive_response():
    if isinstance(message, AssistantMessage):
        if message.error == "rate_limit":
            print("Rate limit hit! Implementing backoff...")
            await asyncio.sleep(60)
            # Retry logic here
        elif message.error:
            print(f"API error: {message.error}")
```

## Testing

-  Passed ruff linting and formatting
-  Passed mypy type checking
-  All existing tests pass

## Type of Change

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)

## Impact

This fix enables production applications to:
- Implement proper error handling for API errors
- Build robust retry logic for rate limits
- Provide better user feedback when errors occur
- Avoid silent failures when the API returns errors

---

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
2025-12-11 10:55:01 -08:00
github-actions[bot]
5b912962e2
chore: release v0.1.15 (#408)
This PR updates the version to 0.1.15 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.15
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.15
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.15/
- Bundled CLI version: 2.0.62
- Install with: `pip install claude-agent-sdk==0.1.15`

🤖 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>
2025-12-11 10:53:45 -08:00
Noah Zweben
53482d8955
feat: add file checkpointing and rewind_files support (#395)
Some checks failed
Lint / lint (push) Has been cancelled
Test / test (macos-latest, 3.10) (push) Has been cancelled
Test / test (macos-latest, 3.11) (push) Has been cancelled
Test / test (macos-latest, 3.12) (push) Has been cancelled
Test / test (macos-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (windows-latest, 3.10) (push) Has been cancelled
Test / test (windows-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.12) (push) Has been cancelled
Test / test (windows-latest, 3.13) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.10) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.11) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.12) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.10) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.11) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.12) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.13) (push) Has been cancelled
Test / test-examples (3.13) (push) Has been cancelled
## Summary

- Add `enable_file_checkpointing` option to `ClaudeAgentOptions`
- Add `rewind_files(user_message_id)` method to `ClaudeSDKClient` and
`Query`
- Add `SDKControlRewindFilesRequest` type for the control protocol
- Set `CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING` env var when enabled

This adds Python SDK support for the file rewind feature from
claude-cli-internal PR #11265.

## Test plan

- [x] Verified imports work correctly
- [x] Verified linting passes (`ruff check`)
- [x] Verified existing tests still pass (106 passed, pre-existing
failures unrelated to this change)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
2025-12-09 10:16:03 -08:00
sarahdeaton
4acfcc2d39
Add license and terms section to README. (#399)
Add "License and terms" section to README clarifying that use of the SDK
is governed by Anthropic's Commercial Terms of Service
2025-12-09 09:33:42 -08:00
github-actions[bot]
b5447d999d
chore: release v0.1.14 (#398)
This PR updates the version to 0.1.14 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.14
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.14
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.14/
- Bundled CLI version: 2.0.62
- Install with: `pip install claude-agent-sdk==0.1.14`

🤖 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>
2025-12-09 08:22:21 -08:00
GitHub Actions
ccff8ddf48 chore: bump bundled CLI version to 2.0.62
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
2025-12-09 02:12:13 +00:00
GitHub Actions
db4a6f7c28 chore: bump bundled CLI version to 2.0.61
Some checks failed
Test / test (macos-latest, 3.10) (push) Has been cancelled
Test / test (macos-latest, 3.11) (push) Has been cancelled
Test / test (macos-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Lint / lint (push) Has been cancelled
Test / test (macos-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (windows-latest, 3.10) (push) Has been cancelled
Test / test (windows-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.12) (push) Has been cancelled
Test / test (windows-latest, 3.13) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.10) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.11) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.12) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.10) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.11) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.12) (push) Has been cancelled
Test / test-examples (3.13) (push) Has been cancelled
2025-12-07 10:47:47 +00:00
Ashwin Bhat
5625286212
fix: move fetch-depth to publish job and use claude-opus-4-5 for changelog (#394)
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
The fetch-depth: 0 was on build-wheels but changelog generation happens
in the publish job. Moved it to the correct location and upgraded the
model for better changelog generation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-06 16:52:09 -08:00
github-actions[bot]
cf6b85fc5d
chore: release v0.1.13 (#393)
Some checks failed
Test / test-examples (3.13) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.10) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.11) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.12) (push) Has been cancelled
Test / test-e2e (macos-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.10) (push) Has been cancelled
Lint / lint (push) Has been cancelled
Test / test (macos-latest, 3.10) (push) Has been cancelled
Test / test (macos-latest, 3.11) (push) Has been cancelled
Test / test (macos-latest, 3.12) (push) Has been cancelled
Test / test (macos-latest, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test (windows-latest, 3.10) (push) Has been cancelled
Test / test (windows-latest, 3.11) (push) Has been cancelled
Test / test (windows-latest, 3.12) (push) Has been cancelled
Test / test (windows-latest, 3.13) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.13) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.10) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.11) (push) Has been cancelled
Test / test-e2e (ubuntu-latest, 3.12) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.11) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.12) (push) Has been cancelled
Test / test-e2e (windows-latest, 3.13) (push) Has been cancelled
This PR updates the version to 0.1.13 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.13
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.13
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.13/
- Bundled CLI version: 2.0.59
- Install with: `pip install claude-agent-sdk==0.1.13`

🤖 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: Ashwin Bhat <ashwin@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-05 16:33:33 -08:00
GitHub Actions
00b5730be6 chore: bump bundled CLI version to 2.0.60 2025-12-06 00:10:43 +00:00
GitHub Actions
1b3e35d14e chore: bump bundled CLI version to 2.0.59
Some checks are pending
Lint / lint (push) Waiting to run
Test / test-examples (3.13) (push) Blocked by required conditions
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
2025-12-04 23:09:33 +00:00
Ramazan Rakhmatullin
69a310cc3f
fix: propagate CLI errors to pending control requests (#388)
## Summary

When the CLI exits with an error (e.g., invalid session ID passed to
`--resume`), signal all pending control requests immediately instead of
waiting for the 60-second timeout.

**The fix adds 4 lines** to the exception handler in `_read_messages`:

```python
# Signal all pending control requests so they fail fast instead of timing out
for request_id, event in list(self.pending_control_responses.items()):
    if request_id not in self.pending_control_results:
        self.pending_control_results[request_id] = e
        event.set()
```

## Problem

When the CLI exits with an error, the SDK's message reader catches it
but doesn't notify pending control requests. This causes `initialize()`
to wait for the full 60-second timeout even though the error is known
within seconds.

Example scenario:
1. User passes invalid session ID via
`ClaudeAgentOptions(resume="invalid-id")`
2. CLI prints `No conversation found with session ID: xxx` and exits
with code 1
3. SDK message reader catches the error after ~3 seconds
4. But `initialize()` keeps waiting for 60 seconds before timing out

## Solution

The existing code at `_send_control_request` lines 361-362 already
handles exceptions in results:
```python
if isinstance(result, Exception):
    raise result
```

The fix simply signals all pending control events when an error occurs,
allowing them to fail fast with the actual error instead of timing out.

## Test Plan

- [ ] Test with invalid session ID - should fail fast (~3s) instead of
timing out (60s)
- [ ] Test normal flow still works
- [ ] Test multiple pending requests all get signaled

Fixes #387
2025-12-04 14:28:36 -08:00
梨梨果
6791efec93
fix: add McpServer runtime placeholder for Pydantic 2.12+ compatibility (#385)
## Summary
- Add runtime placeholder for `McpServer` type to fix Pydantic 2.12+
compatibility
- `McpServer` was only imported under `TYPE_CHECKING`, causing
`PydanticUserError` at runtime

Fixes #384

Co-authored-by: lyrica <lyrica@example.com>
2025-12-04 14:28:24 -08:00
Carlos Cuevas
2d67166cae
fix: add write lock to prevent concurrent transport writes (#391)
## TL;DR
Adds a write lock to `SubprocessCLITransport` to prevent concurrent
writes from parallel subagents.

---

## Overview
When multiple subagents run in parallel and invoke MCP tools, the CLI
sends concurrent `control_request` messages. Each handler tries to write
a response back to the subprocess stdin at the same time. Trio's
`TextSendStream` isn't thread-safe for concurrent access, so this causes
`BusyResourceError`.

This PR adds an `anyio.Lock` around all write operations (`write()`,
`end_input()`, and the stdin-closing part of `close()`). The lock
serializes concurrent writes so they happen one at a time. The `_ready`
flag is now set inside the lock during `close()` to prevent a TOCTOU
race where `write()` checks `_ready`, then `close()` sets it and closes
the stream before `write()` actually sends data.

---

## Call Flow
```mermaid
flowchart TD
    A["write()<br/>subprocess_cli.py:505"] --> B["acquire _write_lock<br/>subprocess_cli.py:507"]
    B --> C["check _ready & stream<br/>subprocess_cli.py:509"]
    C --> D["_stdin_stream.send()<br/>subprocess_cli.py:523"]
    
    E["close()<br/>subprocess_cli.py:458"] --> F["acquire _write_lock<br/>subprocess_cli.py:478"]
    F --> G["set _ready = False<br/>subprocess_cli.py:479"]
    G --> H["close _stdin_stream<br/>subprocess_cli.py:481"]
    
    I["end_input()<br/>subprocess_cli.py:531"] --> J["acquire _write_lock<br/>subprocess_cli.py:533"]
    J --> K["close _stdin_stream<br/>subprocess_cli.py:535"]
```
2025-12-04 14:27:01 -08:00
github-actions[bot]
00332f32dc
chore: release v0.1.12 (#392)
This PR updates the version to 0.1.12 after publishing to PyPI.

## Changes
- Updated version in `pyproject.toml` to 0.1.12
- Updated version in `src/claude_agent_sdk/_version.py` to 0.1.12
- Updated `CHANGELOG.md` with release notes

## Release Information
- Published to PyPI: https://pypi.org/project/claude-agent-sdk/0.1.12/
- Bundled CLI version: 2.0.58
- Install with: `pip install claude-agent-sdk==0.1.12`

🤖 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>
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
2025-12-04 10:30:12 -08:00
Ashwin Bhat
ea0ef25e71
feat: add tools option to ClaudeAgentOptions (#389)
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Add support for the `tools` option matching the TypeScript SDK, which
controls the base set of available tools separately from
allowed/disallowed tool filtering.

Supports three modes:
- Array of tool names: `["Read", "Edit", "Bash"]`
- Empty array: `[]` (disables all built-in tools)
- Preset object: `{"type": "preset", "preset": "claude_code"}`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-04 09:27:02 -08:00
Ashwin Bhat
4e56cb12a9
feat: add SDK beta support with SdkBeta type and betas option (#390)
Port the SdkBeta type and betas option from the TypeScript SDK to enable
SDK users to pass beta feature flags (e.g., 1M context window) to the
CLI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-04 09:26:01 -08:00
GitHub Actions
243703531b chore: bump bundled CLI version to 2.0.58
Some checks are pending
Lint / lint (push) Waiting to run
Test / test (macos-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.10) (push) Waiting to run
Test / test (ubuntu-latest, 3.11) (push) Waiting to run
Test / test (ubuntu-latest, 3.12) (push) Waiting to run
Test / test (ubuntu-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.10) (push) Waiting to run
Test / test (windows-latest, 3.13) (push) Waiting to run
Test / test-e2e (macos-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (macos-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.10) (push) Blocked by required conditions
Test / test-examples (3.13) (push) Blocked by required conditions
Test / test (macos-latest, 3.10) (push) Waiting to run
Test / test (macos-latest, 3.11) (push) Waiting to run
Test / test (macos-latest, 3.13) (push) Waiting to run
Test / test (windows-latest, 3.11) (push) Waiting to run
Test / test (windows-latest, 3.12) (push) Waiting to run
Test / test-e2e (windows-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.11) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.12) (push) Blocked by required conditions
Test / test-e2e (ubuntu-latest, 3.13) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.10) (push) Blocked by required conditions
Test / test-e2e (windows-latest, 3.11) (push) Blocked by required conditions
2025-12-03 20:09:56 +00:00
23 changed files with 927 additions and 78 deletions

View file

@ -0,0 +1,9 @@
---
name: test-agent
description: A simple test agent for SDK testing
tools: Read
---
# Test Agent
You are a simple test agent. When asked a question, provide a brief, helpful answer.

49
.dockerignore Normal file
View file

@ -0,0 +1,49 @@
# Git
.git
.gitignore
# Python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
.env
.venv
env/
venv/
ENV/
# IDE
.idea/
.vscode/
*.swp
*.swo
# Testing/Coverage
.coverage
.pytest_cache/
htmlcov/
.tox/
.nox/
# Misc
*.log
.DS_Store

View file

@ -24,12 +24,6 @@ jobs:
VERSION="${BRANCH_NAME#release/v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Get previous release tag
id: previous_tag
run: |
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
- name: Create and push tag
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
@ -46,14 +40,34 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Create release with auto-generated notes
gh release create "v${{ steps.extract_version.outputs.version }}" \
--title "Release v${{ steps.extract_version.outputs.version }}" \
--generate-notes \
--notes-start-tag "${{ steps.previous_tag.outputs.previous_tag }}" \
--notes "Published to PyPI: https://pypi.org/project/claude-agent-sdk/${{ steps.extract_version.outputs.version }}/
VERSION="${{ steps.extract_version.outputs.version }}"
### Installation
\`\`\`bash
pip install claude-agent-sdk==${{ steps.extract_version.outputs.version }}
\`\`\`"
# Extract changelog section for this version to a temp file
awk -v ver="$VERSION" '
/^## / {
if (found) exit
if ($2 == ver) found=1
next
}
found { print }
' CHANGELOG.md > release_notes.md
# Append install instructions
{
echo ""
echo "---"
echo ""
echo "**PyPI:** https://pypi.org/project/claude-agent-sdk/VERSION/"
echo ""
echo '```bash'
echo "pip install claude-agent-sdk==VERSION"
echo '```'
} >> release_notes.md
# Replace VERSION placeholder
sed -i "s/VERSION/$VERSION/g" release_notes.md
# Create release with notes from file
gh release create "v$VERSION" \
--title "v$VERSION" \
--notes-file release_notes.md

View file

@ -68,8 +68,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history including tags (necessary for changelog generation)
- name: Set up Python
uses: actions/setup-python@v5
@ -109,6 +107,7 @@ jobs:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # Fetch all history including tags for changelog generation
- name: Set up Python
uses: actions/setup-python@v5
@ -189,6 +188,7 @@ jobs:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_args: |
--model claude-opus-4-5
--allowedTools 'Bash(git add:*),Bash(git commit:*),Edit'
- name: Push branch and create PR

View file

@ -81,6 +81,24 @@ jobs:
run: |
python -m pytest e2e-tests/ -v -m e2e
test-e2e-docker:
runs-on: ubuntu-latest
needs: test # Run after unit tests pass
# Run e2e tests in Docker to catch container-specific issues like #406
steps:
- uses: actions/checkout@v4
- name: Build Docker test image
run: docker build -f Dockerfile.test -t claude-sdk-test .
- name: Run e2e tests in Docker
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
docker run --rm -e ANTHROPIC_API_KEY \
claude-sdk-test python -m pytest e2e-tests/ -v -m e2e
test-examples:
runs-on: ubuntu-latest
needs: test-e2e # Run after e2e tests

View file

@ -1,5 +1,70 @@
# Changelog
## 0.1.18
### Internal/Other Changes
- **Docker-based test infrastructure**: Added Docker support for running e2e tests in containerized environments, helping catch Docker-specific issues (#424)
- Updated bundled Claude CLI to version 2.0.72
## 0.1.17
### New Features
- **UserMessage UUID field**: Added `uuid` field to `UserMessage` response type, making it easier to use the `rewind_files()` method by providing direct access to message identifiers needed for file checkpointing (#418)
### Internal/Other Changes
- Updated bundled Claude CLI to version 2.0.70
## 0.1.16
### Bug Fixes
- **Rate limit detection**: Fixed parsing of the `error` field in `AssistantMessage`, enabling applications to detect and handle API errors like rate limits. Previously, the `error` field was defined but never populated from CLI responses (#405)
### Internal/Other Changes
- Updated bundled Claude CLI to version 2.0.68
## 0.1.15
### New Features
- **File checkpointing and rewind**: Added `enable_file_checkpointing` option to `ClaudeAgentOptions` and `rewind_files(user_message_id)` method to `ClaudeSDKClient` and `Query`. This enables reverting file changes made during a session back to a specific checkpoint, useful for exploring different approaches or recovering from unwanted modifications (#395)
### Documentation
- Added license and terms section to README (#399)
## 0.1.14
### Internal/Other Changes
- Updated bundled Claude CLI to version 2.0.62
## 0.1.13
### Bug Fixes
- **Faster error handling**: CLI errors (e.g., invalid session ID) now propagate to pending requests immediately instead of waiting for the 60-second timeout (#388)
- **Pydantic 2.12+ compatibility**: Fixed `PydanticUserError` caused by `McpServer` type only being imported under `TYPE_CHECKING` (#385)
- **Concurrent subagent writes**: Added write lock to prevent `BusyResourceError` when multiple subagents invoke MCP tools in parallel (#391)
### Internal/Other Changes
- Updated bundled Claude CLI to version 2.0.59
## 0.1.12
### New Features
- **Tools option**: Added `tools` option to `ClaudeAgentOptions` for controlling the base set of available tools, matching the TypeScript SDK functionality. Supports three modes:
- Array of tool names to specify which tools should be available (e.g., `["Read", "Edit", "Bash"]`)
- Empty array `[]` to disable all built-in tools
- Preset object `{"type": "preset", "preset": "claude_code"}` to use the default Claude Code toolset
- **SDK beta support**: Added `betas` option to `ClaudeAgentOptions` for enabling Anthropic API beta features. Currently supports `"context-1m-2025-08-07"` for extended context window
## 0.1.11
### Internal/Other Changes

29
Dockerfile.test Normal file
View file

@ -0,0 +1,29 @@
# Dockerfile for running SDK tests in a containerized environment
# This helps catch Docker-specific issues like #406
FROM python:3.12-slim
# Install dependencies for Claude CLI and git (needed for some tests)
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install Claude Code CLI
RUN curl -fsSL https://claude.ai/install.sh | bash
ENV PATH="/root/.local/bin:$PATH"
# Set up working directory
WORKDIR /app
# Copy the SDK source
COPY . .
# Install SDK with dev dependencies
RUN pip install -e ".[dev]"
# Verify CLI installation
RUN claude -v
# Default: run unit tests
CMD ["python", "-m", "pytest", "tests/", "-v"]

View file

@ -351,6 +351,6 @@ The package is published to PyPI via the GitHub Actions workflow in `.github/wor
The workflow tracks both the package version and the bundled CLI version separately, allowing you to release a new package version with an updated CLI without code changes.
## License
## License and terms
MIT
Use of this SDK is governed by Anthropic's [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms), including when you use it to power products and services that you make available to your own customers and end users, except to the extent a specific component or dependency is covered by a different license as indicated in that component's LICENSE file.

View file

@ -38,15 +38,88 @@ async def test_agent_definition():
async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init":
agents = message.data.get("agents", [])
assert isinstance(
agents, list
), f"agents should be a list of strings, got: {type(agents)}"
assert (
"test-agent" in agents
), f"test-agent should be available, got: {agents}"
assert isinstance(agents, list), (
f"agents should be a list of strings, got: {type(agents)}"
)
assert "test-agent" in agents, (
f"test-agent should be available, got: {agents}"
)
break
@pytest.mark.e2e
@pytest.mark.asyncio
async def test_filesystem_agent_loading():
"""Test that filesystem-based agents load via setting_sources and produce full response.
This is the core test for issue #406. It verifies that when using
setting_sources=["project"] with a .claude/agents/ directory containing
agent definitions, the SDK:
1. Loads the agents (they appear in init message)
2. Produces a full response with AssistantMessage
3. Completes with a ResultMessage
The bug in #406 causes the iterator to complete after only the
init SystemMessage, never yielding AssistantMessage or ResultMessage.
"""
with tempfile.TemporaryDirectory() as tmpdir:
# Create a temporary project with a filesystem agent
project_dir = Path(tmpdir)
agents_dir = project_dir / ".claude" / "agents"
agents_dir.mkdir(parents=True)
# Create a test agent file
agent_file = agents_dir / "fs-test-agent.md"
agent_file.write_text(
"""---
name: fs-test-agent
description: A filesystem test agent for SDK testing
tools: Read
---
# Filesystem Test Agent
You are a simple test agent. When asked a question, provide a brief, helpful answer.
"""
)
options = ClaudeAgentOptions(
setting_sources=["project"],
cwd=project_dir,
max_turns=1,
)
messages = []
async with ClaudeSDKClient(options=options) as client:
await client.query("Say hello in exactly 3 words")
async for msg in client.receive_response():
messages.append(msg)
# Must have at least init, assistant, result
message_types = [type(m).__name__ for m in messages]
assert "SystemMessage" in message_types, "Missing SystemMessage (init)"
assert "AssistantMessage" in message_types, (
f"Missing AssistantMessage - got only: {message_types}. "
"This may indicate issue #406 (silent failure with filesystem agents)."
)
assert "ResultMessage" in message_types, "Missing ResultMessage"
# Find the init message and check for the filesystem agent
for msg in messages:
if isinstance(msg, SystemMessage) and msg.subtype == "init":
agents = msg.data.get("agents", [])
# Agents are returned as strings (just names)
assert "fs-test-agent" in agents, (
f"fs-test-agent not loaded from filesystem. Found: {agents}"
)
break
# On Windows, wait for file handles to be released before cleanup
if sys.platform == "win32":
await asyncio.sleep(0.5)
@pytest.mark.e2e
@pytest.mark.asyncio
async def test_setting_sources_default():
@ -74,12 +147,12 @@ async def test_setting_sources_default():
async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init":
output_style = message.data.get("output_style")
assert (
output_style != "local-test-style"
), f"outputStyle should NOT be from local settings (default is no settings), got: {output_style}"
assert (
output_style == "default"
), f"outputStyle should be 'default', got: {output_style}"
assert output_style != "local-test-style", (
f"outputStyle should NOT be from local settings (default is no settings), got: {output_style}"
)
assert output_style == "default", (
f"outputStyle should be 'default', got: {output_style}"
)
break
# On Windows, wait for file handles to be released before cleanup
@ -121,9 +194,9 @@ This is a test command.
async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init":
commands = message.data.get("slash_commands", [])
assert (
"testcmd" not in commands
), f"testcmd should NOT be available with user-only sources, got: {commands}"
assert "testcmd" not in commands, (
f"testcmd should NOT be available with user-only sources, got: {commands}"
)
break
# On Windows, wait for file handles to be released before cleanup
@ -159,11 +232,11 @@ async def test_setting_sources_project_included():
async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init":
output_style = message.data.get("output_style")
assert (
output_style == "local-test-style"
), f"outputStyle should be from local settings, got: {output_style}"
assert output_style == "local-test-style", (
f"outputStyle should be from local settings, got: {output_style}"
)
break
# On Windows, wait for file handles to be released before cleanup
if sys.platform == "win32":
await asyncio.sleep(0.5)
await asyncio.sleep(0.5)

View file

@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""Example of loading filesystem-based agents via setting_sources.
This example demonstrates how to load agents defined in .claude/agents/ files
using the setting_sources option. This is different from inline AgentDefinition
objects - these agents are loaded from markdown files on disk.
This example tests the scenario from issue #406 where filesystem-based agents
loaded via setting_sources=["project"] may silently fail in certain environments.
Usage:
./examples/filesystem_agents.py
"""
import asyncio
from pathlib import Path
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
SystemMessage,
TextBlock,
)
def extract_agents(msg: SystemMessage) -> list[str]:
"""Extract agent names from system message init data."""
if msg.subtype == "init":
agents = msg.data.get("agents", [])
# Agents can be either strings or dicts with a 'name' field
result = []
for a in agents:
if isinstance(a, str):
result.append(a)
elif isinstance(a, dict):
result.append(a.get("name", ""))
return result
return []
async def main():
"""Test loading filesystem-based agents."""
print("=== Filesystem Agents Example ===")
print("Testing: setting_sources=['project'] with .claude/agents/test-agent.md")
print()
# Use the SDK repo directory which has .claude/agents/test-agent.md
sdk_dir = Path(__file__).parent.parent
options = ClaudeAgentOptions(
setting_sources=["project"],
cwd=sdk_dir,
)
message_types: list[str] = []
agents_found: list[str] = []
async with ClaudeSDKClient(options=options) as client:
await client.query("Say hello in exactly 3 words")
async for msg in client.receive_response():
message_types.append(type(msg).__name__)
if isinstance(msg, SystemMessage) and msg.subtype == "init":
agents_found = extract_agents(msg)
print(f"Init message received. Agents loaded: {agents_found}")
elif isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"Assistant: {block.text}")
elif isinstance(msg, ResultMessage):
print(
f"Result: subtype={msg.subtype}, cost=${msg.total_cost_usd or 0:.4f}"
)
print()
print("=== Summary ===")
print(f"Message types received: {message_types}")
print(f"Total messages: {len(message_types)}")
# Validate the results
has_init = "SystemMessage" in message_types
has_assistant = "AssistantMessage" in message_types
has_result = "ResultMessage" in message_types
has_test_agent = "test-agent" in agents_found
print()
if has_init and has_assistant and has_result:
print("SUCCESS: Received full response (init, assistant, result)")
else:
print("FAILURE: Did not receive full response")
print(f" - Init: {has_init}")
print(f" - Assistant: {has_assistant}")
print(f" - Result: {has_result}")
if has_test_agent:
print("SUCCESS: test-agent was loaded from filesystem")
else:
print("WARNING: test-agent was NOT loaded (may not exist in .claude/agents/)")
if __name__ == "__main__":
asyncio.run(main())

111
examples/tools_option.py Normal file
View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Example demonstrating the tools option and verifying tools in system message."""
import anyio
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ResultMessage,
SystemMessage,
TextBlock,
query,
)
async def tools_array_example():
"""Example with tools as array of specific tool names."""
print("=== Tools Array Example ===")
print("Setting tools=['Read', 'Glob', 'Grep']")
print()
options = ClaudeAgentOptions(
tools=["Read", "Glob", "Grep"],
max_turns=1,
)
async for message in query(
prompt="What tools do you have available? Just list them briefly.",
options=options,
):
if isinstance(message, SystemMessage) and message.subtype == "init":
tools = message.data.get("tools", [])
print(f"Tools from system message: {tools}")
print()
elif isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(message, ResultMessage):
if message.total_cost_usd:
print(f"\nCost: ${message.total_cost_usd:.4f}")
print()
async def tools_empty_array_example():
"""Example with tools as empty array (disables all built-in tools)."""
print("=== Tools Empty Array Example ===")
print("Setting tools=[] (disables all built-in tools)")
print()
options = ClaudeAgentOptions(
tools=[],
max_turns=1,
)
async for message in query(
prompt="What tools do you have available? Just list them briefly.",
options=options,
):
if isinstance(message, SystemMessage) and message.subtype == "init":
tools = message.data.get("tools", [])
print(f"Tools from system message: {tools}")
print()
elif isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(message, ResultMessage):
if message.total_cost_usd:
print(f"\nCost: ${message.total_cost_usd:.4f}")
print()
async def tools_preset_example():
"""Example with tools preset (all default Claude Code tools)."""
print("=== Tools Preset Example ===")
print("Setting tools={'type': 'preset', 'preset': 'claude_code'}")
print()
options = ClaudeAgentOptions(
tools={"type": "preset", "preset": "claude_code"},
max_turns=1,
)
async for message in query(
prompt="What tools do you have available? Just list them briefly.",
options=options,
):
if isinstance(message, SystemMessage) and message.subtype == "init":
tools = message.data.get("tools", [])
print(f"Tools from system message ({len(tools)} tools): {tools[:5]}...")
print()
elif isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(message, ResultMessage):
if message.total_cost_usd:
print(f"\nCost: ${message.total_cost_usd:.4f}")
print()
async def main():
"""Run all examples."""
await tools_array_example()
await tools_empty_array_example()
await tools_preset_example()
if __name__ == "__main__":
anyio.run(main)

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "claude-agent-sdk"
version = "0.1.11"
version = "0.1.18"
description = "Python SDK for Claude Code"
readme = "README.md"
requires-python = ">=3.10"

77
scripts/test-docker.sh Executable file
View file

@ -0,0 +1,77 @@
#!/bin/bash
# Run SDK tests in a Docker container
# This helps catch Docker-specific issues like #406
#
# Usage:
# ./scripts/test-docker.sh [unit|e2e|all]
#
# Examples:
# ./scripts/test-docker.sh unit # Run unit tests only
# ANTHROPIC_API_KEY=sk-... ./scripts/test-docker.sh e2e # Run e2e tests
# ANTHROPIC_API_KEY=sk-... ./scripts/test-docker.sh all # Run all tests
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"
usage() {
echo "Usage: $0 [unit|e2e|all]"
echo ""
echo "Commands:"
echo " unit - Run unit tests only (no API key needed)"
echo " e2e - Run e2e tests (requires ANTHROPIC_API_KEY)"
echo " all - Run both unit and e2e tests"
echo ""
echo "Examples:"
echo " $0 unit"
echo " ANTHROPIC_API_KEY=sk-... $0 e2e"
exit 1
}
echo "Building Docker test image..."
docker build -f Dockerfile.test -t claude-sdk-test .
case "${1:-unit}" in
unit)
echo ""
echo "Running unit tests in Docker..."
docker run --rm claude-sdk-test \
python -m pytest tests/ -v
;;
e2e)
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "Error: ANTHROPIC_API_KEY environment variable is required for e2e tests"
echo ""
echo "Usage: ANTHROPIC_API_KEY=sk-... $0 e2e"
exit 1
fi
echo ""
echo "Running e2e tests in Docker..."
docker run --rm -e ANTHROPIC_API_KEY \
claude-sdk-test python -m pytest e2e-tests/ -v -m e2e
;;
all)
echo ""
echo "Running unit tests in Docker..."
docker run --rm claude-sdk-test \
python -m pytest tests/ -v
echo ""
if [ -n "$ANTHROPIC_API_KEY" ]; then
echo "Running e2e tests in Docker..."
docker run --rm -e ANTHROPIC_API_KEY \
claude-sdk-test python -m pytest e2e-tests/ -v -m e2e
else
echo "Skipping e2e tests (ANTHROPIC_API_KEY not set)"
fi
;;
*)
usage
;;
esac
echo ""
echo "Done!"

View file

@ -42,6 +42,7 @@ from .types import (
SandboxIgnoreViolations,
SandboxNetworkConfig,
SandboxSettings,
SdkBeta,
SdkPluginConfig,
SettingSource,
StopHookInput,
@ -345,6 +346,8 @@ __all__ = [
"SettingSource",
# Plugin support
"SdkPluginConfig",
# Beta support
"SdkBeta",
# Sandbox support
"SandboxSettings",
"SandboxNetworkConfig",

View file

@ -1,3 +1,3 @@
"""Bundled Claude Code CLI version."""
__cli_version__ = "2.0.57"
__cli_version__ = "2.0.74"

View file

@ -48,6 +48,7 @@ def parse_message(data: dict[str, Any]) -> Message:
case "user":
try:
parent_tool_use_id = data.get("parent_tool_use_id")
uuid = data.get("uuid")
if isinstance(data["message"]["content"], list):
user_content_blocks: list[ContentBlock] = []
for block in data["message"]["content"]:
@ -74,10 +75,12 @@ def parse_message(data: dict[str, Any]) -> Message:
)
return UserMessage(
content=user_content_blocks,
uuid=uuid,
parent_tool_use_id=parent_tool_use_id,
)
return UserMessage(
content=data["message"]["content"],
uuid=uuid,
parent_tool_use_id=parent_tool_use_id,
)
except KeyError as e:
@ -120,6 +123,7 @@ def parse_message(data: dict[str, Any]) -> Message:
content=content_blocks,
model=data["message"]["model"],
parent_tool_use_id=data.get("parent_tool_use_id"),
error=data["message"].get("error"),
)
except KeyError as e:
raise MessageParseError(

View file

@ -214,6 +214,11 @@ class Query:
raise # Re-raise to properly handle cancellation
except Exception as e:
logger.error(f"Fatal error in message reader: {e}")
# Signal all pending control requests so they fail fast instead of timing out
for request_id, event in list(self.pending_control_responses.items()):
if request_id not in self.pending_control_results:
self.pending_control_results[request_id] = e
event.set()
# Put error in stream so iterators can handle it
await self._message_send.send({"type": "error", "error": str(e)})
finally:
@ -534,6 +539,21 @@ class Query:
}
)
async def rewind_files(self, user_message_id: str) -> None:
"""Rewind tracked files to their state at a specific user message.
Requires file checkpointing to be enabled via the `enable_file_checkpointing` option.
Args:
user_message_id: UUID of the user message to rewind to
"""
await self._send_control_request(
{
"subtype": "rewind_files",
"user_message_id": user_message_id,
}
)
async def stream_input(self, stream: AsyncIterable[dict[str, Any]]) -> None:
"""Stream input messages to transport.

View file

@ -65,6 +65,7 @@ class SubprocessCLITransport(Transport):
else _DEFAULT_MAX_BUFFER_SIZE
)
self._temp_files: list[str] = [] # Track temporary files for cleanup
self._write_lock: anyio.Lock = anyio.Lock()
def _find_cli(self) -> str:
"""Find Claude Code CLI binary."""
@ -185,6 +186,18 @@ class SubprocessCLITransport(Transport):
["--append-system-prompt", self._options.system_prompt["append"]]
)
# Handle tools option (base set of tools)
if self._options.tools is not None:
tools = self._options.tools
if isinstance(tools, list):
if len(tools) == 0:
cmd.extend(["--tools", ""])
else:
cmd.extend(["--tools", ",".join(tools)])
else:
# Preset object - 'claude_code' preset maps to 'default'
cmd.extend(["--tools", "default"])
if self._options.allowed_tools:
cmd.extend(["--allowedTools", ",".join(self._options.allowed_tools)])
@ -203,6 +216,9 @@ class SubprocessCLITransport(Transport):
if self._options.fallback_model:
cmd.extend(["--fallback-model", self._options.fallback_model])
if self._options.betas:
cmd.extend(["--betas", ",".join(self._options.betas)])
if self._options.permission_prompt_tool_name:
cmd.extend(
["--permission-prompt-tool", self._options.permission_prompt_tool_name]
@ -368,6 +384,10 @@ class SubprocessCLITransport(Transport):
"CLAUDE_AGENT_SDK_VERSION": __version__,
}
# Enable file checkpointing if requested
if self._options.enable_file_checkpointing:
process_env["CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING"] = "true"
if self._cwd:
process_env["PWD"] = self._cwd
@ -456,8 +476,6 @@ class SubprocessCLITransport(Transport):
async def close(self) -> None:
"""Close the transport and clean up resources."""
self._ready = False
# Clean up temporary files first (before early return)
for temp_file in self._temp_files:
with suppress(Exception):
@ -465,6 +483,7 @@ class SubprocessCLITransport(Transport):
self._temp_files.clear()
if not self._process:
self._ready = False
return
# Close stderr task group if active
@ -474,21 +493,19 @@ class SubprocessCLITransport(Transport):
await self._stderr_task_group.__aexit__(None, None, None)
self._stderr_task_group = None
# Close streams
if self._stdin_stream:
with suppress(Exception):
await self._stdin_stream.aclose()
self._stdin_stream = None
# Close stdin stream (acquire lock to prevent race with concurrent writes)
async with self._write_lock:
self._ready = False # Set inside lock to prevent TOCTOU with write()
if self._stdin_stream:
with suppress(Exception):
await self._stdin_stream.aclose()
self._stdin_stream = None
if self._stderr_stream:
with suppress(Exception):
await self._stderr_stream.aclose()
self._stderr_stream = None
if self._process.stdin:
with suppress(Exception):
await self._process.stdin.aclose()
# Terminate and wait for process
if self._process.returncode is None:
with suppress(ProcessLookupError):
@ -506,37 +523,37 @@ class SubprocessCLITransport(Transport):
async def write(self, data: str) -> None:
"""Write raw data to the transport."""
# Check if ready (like TypeScript)
if not self._ready or not self._stdin_stream:
raise CLIConnectionError("ProcessTransport is not ready for writing")
async with self._write_lock:
# All checks inside lock to prevent TOCTOU races with close()/end_input()
if not self._ready or not self._stdin_stream:
raise CLIConnectionError("ProcessTransport is not ready for writing")
# Check if process is still alive (like TypeScript)
if self._process and self._process.returncode is not None:
raise CLIConnectionError(
f"Cannot write to terminated process (exit code: {self._process.returncode})"
)
if self._process and self._process.returncode is not None:
raise CLIConnectionError(
f"Cannot write to terminated process (exit code: {self._process.returncode})"
)
# Check for exit errors (like TypeScript)
if self._exit_error:
raise CLIConnectionError(
f"Cannot write to process that exited with error: {self._exit_error}"
) from self._exit_error
if self._exit_error:
raise CLIConnectionError(
f"Cannot write to process that exited with error: {self._exit_error}"
) from self._exit_error
try:
await self._stdin_stream.send(data)
except Exception as e:
self._ready = False # Mark as not ready (like TypeScript)
self._exit_error = CLIConnectionError(
f"Failed to write to process stdin: {e}"
)
raise self._exit_error from e
try:
await self._stdin_stream.send(data)
except Exception as e:
self._ready = False
self._exit_error = CLIConnectionError(
f"Failed to write to process stdin: {e}"
)
raise self._exit_error from e
async def end_input(self) -> None:
"""End the input stream (close stdin)."""
if self._stdin_stream:
with suppress(Exception):
await self._stdin_stream.aclose()
self._stdin_stream = None
async with self._write_lock:
if self._stdin_stream:
with suppress(Exception):
await self._stdin_stream.aclose()
self._stdin_stream = None
def read_messages(self) -> AsyncIterator[dict[str, Any]]:
"""Read and parse messages from the transport."""

View file

@ -1,3 +1,3 @@
"""Version information for claude-agent-sdk."""
__version__ = "0.1.11"
__version__ = "0.1.18"

View file

@ -261,6 +261,38 @@ class ClaudeSDKClient:
raise CLIConnectionError("Not connected. Call connect() first.")
await self._query.set_model(model)
async def rewind_files(self, user_message_id: str) -> None:
"""Rewind tracked files to their state at a specific user message.
Requires:
- `enable_file_checkpointing=True` to track file changes
- `extra_args={"replay-user-messages": None}` to receive UserMessage
objects with `uuid` in the response stream
Args:
user_message_id: UUID of the user message to rewind to. This should be
the `uuid` field from a `UserMessage` received during the conversation.
Example:
```python
options = ClaudeAgentOptions(
enable_file_checkpointing=True,
extra_args={"replay-user-messages": None},
)
async with ClaudeSDKClient(options) as client:
await client.query("Make some changes to my files")
async for msg in client.receive_response():
if isinstance(msg, UserMessage) and msg.uuid:
checkpoint_id = msg.uuid # Save this for later
# Later, rewind to that point
await client.rewind_files(checkpoint_id)
```
"""
if not self._query:
raise CLIConnectionError("Not connected. Call connect() first.")
await self._query.rewind_files(user_message_id)
async def get_server_info(self) -> dict[str, Any] | None:
"""Get server initialization info including available commands and output styles.

View file

@ -10,10 +10,16 @@ from typing_extensions import NotRequired
if TYPE_CHECKING:
from mcp.server import Server as McpServer
else:
# Runtime placeholder for forward reference resolution in Pydantic 2.12+
McpServer = Any
# Permission modes
PermissionMode = Literal["default", "acceptEdits", "plan", "bypassPermissions"]
# SDK Beta features - see https://docs.anthropic.com/en/api/beta-headers
SdkBeta = Literal["context-1m-2025-08-07"]
# Agent definitions
SettingSource = Literal["user", "project", "local"]
@ -26,6 +32,13 @@ class SystemPromptPreset(TypedDict):
append: NotRequired[str]
class ToolsPreset(TypedDict):
"""Tools preset configuration."""
type: Literal["preset"]
preset: Literal["claude_code"]
@dataclass
class AgentDefinition:
"""Agent definition configuration."""
@ -549,6 +562,7 @@ class UserMessage:
"""User message."""
content: str | list[ContentBlock]
uuid: str | None = None
parent_tool_use_id: str | None = None
@ -603,6 +617,7 @@ Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage | Strea
class ClaudeAgentOptions:
"""Query options for Claude SDK."""
tools: list[str] | ToolsPreset | None = None
allowed_tools: list[str] = field(default_factory=list)
system_prompt: str | SystemPromptPreset | None = None
mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
@ -614,6 +629,8 @@ class ClaudeAgentOptions:
disallowed_tools: list[str] = field(default_factory=list)
model: str | None = None
fallback_model: str | None = None
# Beta features - see https://docs.anthropic.com/en/api/beta-headers
betas: list[SdkBeta] = field(default_factory=list)
permission_prompt_tool_name: str | None = None
cwd: str | Path | None = None
cli_path: str | Path | None = None
@ -657,6 +674,10 @@ class ClaudeAgentOptions:
# Output format for structured outputs (matches Messages API structure)
# Example: {"type": "json_schema", "schema": {"type": "object", "properties": {...}}}
output_format: dict[str, Any] | None = None
# Enable file checkpointing to track file changes during the session.
# When enabled, files can be rewound to their state at any user message
# using `ClaudeSDKClient.rewind_files()`.
enable_file_checkpointing: bool = False
# SDK Control Protocol
@ -697,6 +718,11 @@ class SDKControlMcpMessageRequest(TypedDict):
message: Any
class SDKControlRewindFilesRequest(TypedDict):
subtype: Literal["rewind_files"]
user_message_id: str
class SDKControlRequest(TypedDict):
type: Literal["control_request"]
request_id: str
@ -707,6 +733,7 @@ class SDKControlRequest(TypedDict):
| SDKControlSetPermissionModeRequest
| SDKHookCallbackRequest
| SDKControlMcpMessageRequest
| SDKControlRewindFilesRequest
)

View file

@ -31,6 +31,21 @@ class TestMessageParser:
assert isinstance(message.content[0], TextBlock)
assert message.content[0].text == "Hello"
def test_parse_user_message_with_uuid(self):
"""Test parsing a user message with uuid field (issue #414).
The uuid field is needed for file checkpointing with rewind_files().
"""
data = {
"type": "user",
"uuid": "msg-abc123-def456",
"message": {"content": [{"type": "text", "text": "Hello"}]},
}
message = parse_message(data)
assert isinstance(message, UserMessage)
assert message.uuid == "msg-abc123-def456"
assert len(message.content) == 1
def test_parse_user_message_with_tool_use(self):
"""Test parsing a user message with tool_use block."""
data = {

View file

@ -647,3 +647,182 @@ class TestSubprocessCLITransport:
assert network["allowLocalBinding"] is True
assert network["httpProxyPort"] == 8080
assert network["socksProxyPort"] == 8081
def test_build_command_with_tools_array(self):
"""Test building CLI command with tools as array of tool names."""
transport = SubprocessCLITransport(
prompt="test",
options=make_options(tools=["Read", "Edit", "Bash"]),
)
cmd = transport._build_command()
assert "--tools" in cmd
tools_idx = cmd.index("--tools")
assert cmd[tools_idx + 1] == "Read,Edit,Bash"
def test_build_command_with_tools_empty_array(self):
"""Test building CLI command with tools as empty array (disables all tools)."""
transport = SubprocessCLITransport(
prompt="test",
options=make_options(tools=[]),
)
cmd = transport._build_command()
assert "--tools" in cmd
tools_idx = cmd.index("--tools")
assert cmd[tools_idx + 1] == ""
def test_build_command_with_tools_preset(self):
"""Test building CLI command with tools preset."""
transport = SubprocessCLITransport(
prompt="test",
options=make_options(tools={"type": "preset", "preset": "claude_code"}),
)
cmd = transport._build_command()
assert "--tools" in cmd
tools_idx = cmd.index("--tools")
assert cmd[tools_idx + 1] == "default"
def test_build_command_without_tools(self):
"""Test building CLI command without tools option (default None)."""
transport = SubprocessCLITransport(
prompt="test",
options=make_options(),
)
cmd = transport._build_command()
assert "--tools" not in cmd
def test_concurrent_writes_are_serialized(self):
"""Test that concurrent write() calls are serialized by the lock.
When parallel subagents invoke MCP tools, they trigger concurrent write()
calls. Without the _write_lock, trio raises BusyResourceError.
Uses a real subprocess with the same stream setup as production:
process.stdin -> TextSendStream
"""
async def _test():
import sys
from subprocess import PIPE
from anyio.streams.text import TextSendStream
# Create a real subprocess that consumes stdin (cross-platform)
process = await anyio.open_process(
[sys.executable, "-c", "import sys; sys.stdin.read()"],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
)
try:
transport = SubprocessCLITransport(
prompt="test",
options=ClaudeAgentOptions(cli_path="/usr/bin/claude"),
)
# Same setup as production: TextSendStream wrapping process.stdin
transport._ready = True
transport._process = MagicMock(returncode=None)
transport._stdin_stream = TextSendStream(process.stdin)
# Spawn concurrent writes - the lock should serialize them
num_writes = 10
errors: list[Exception] = []
async def do_write(i: int):
try:
await transport.write(f'{{"msg": {i}}}\n')
except Exception as e:
errors.append(e)
async with anyio.create_task_group() as tg:
for i in range(num_writes):
tg.start_soon(do_write, i)
# All writes should succeed - the lock serializes them
assert len(errors) == 0, f"Got errors: {errors}"
finally:
process.terminate()
await process.wait()
anyio.run(_test, backend="trio")
def test_concurrent_writes_fail_without_lock(self):
"""Verify that without the lock, concurrent writes cause BusyResourceError.
Uses a real subprocess with the same stream setup as production.
"""
async def _test():
import sys
from contextlib import asynccontextmanager
from subprocess import PIPE
from anyio.streams.text import TextSendStream
# Create a real subprocess that consumes stdin (cross-platform)
process = await anyio.open_process(
[sys.executable, "-c", "import sys; sys.stdin.read()"],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
)
try:
transport = SubprocessCLITransport(
prompt="test",
options=ClaudeAgentOptions(cli_path="/usr/bin/claude"),
)
# Same setup as production
transport._ready = True
transport._process = MagicMock(returncode=None)
transport._stdin_stream = TextSendStream(process.stdin)
# Replace lock with no-op to trigger the race condition
class NoOpLock:
@asynccontextmanager
async def __call__(self):
yield
async def __aenter__(self):
return self
async def __aexit__(self, *args):
pass
transport._write_lock = NoOpLock()
# Spawn concurrent writes - should fail without lock
num_writes = 10
errors: list[Exception] = []
async def do_write(i: int):
try:
await transport.write(f'{{"msg": {i}}}\n')
except Exception as e:
errors.append(e)
async with anyio.create_task_group() as tg:
for i in range(num_writes):
tg.start_soon(do_write, i)
# Should have gotten errors due to concurrent access
assert len(errors) > 0, (
"Expected errors from concurrent access, but got none"
)
# Check that at least one error mentions the concurrent access
error_strs = [str(e) for e in errors]
assert any("another task" in s for s in error_strs), (
f"Expected 'another task' error, got: {error_strs}"
)
finally:
process.terminate()
await process.wait()
anyio.run(_test, backend="trio")