Compare commits

...

15 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
16 changed files with 470 additions and 42 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}" VERSION="${BRANCH_NAME#release/v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT 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 - name: Create and push tag
run: | run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.email "github-actions[bot]@users.noreply.github.com"
@ -46,14 +40,34 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
# Create release with auto-generated notes VERSION="${{ steps.extract_version.outputs.version }}"
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 }}/
### Installation # Extract changelog section for this version to a temp file
\`\`\`bash awk -v ver="$VERSION" '
pip install claude-agent-sdk==${{ steps.extract_version.outputs.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

@ -81,6 +81,24 @@ jobs:
run: | run: |
python -m pytest e2e-tests/ -v -m e2e 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: test-examples:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: test-e2e # Run after e2e tests needs: test-e2e # Run after e2e tests

View file

@ -1,5 +1,32 @@
# Changelog # 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 ## 0.1.15
### New Features ### New Features

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

@ -38,15 +38,88 @@ async def test_agent_definition():
async for message in client.receive_response(): async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init": if isinstance(message, SystemMessage) and message.subtype == "init":
agents = message.data.get("agents", []) agents = message.data.get("agents", [])
assert isinstance( assert isinstance(agents, list), (
agents, list f"agents should be a list of strings, got: {type(agents)}"
), f"agents should be a list of strings, got: {type(agents)}" )
assert ( assert "test-agent" in agents, (
"test-agent" in agents f"test-agent should be available, got: {agents}"
), f"test-agent should be available, got: {agents}" )
break 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.e2e
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_setting_sources_default(): async def test_setting_sources_default():
@ -74,12 +147,12 @@ async def test_setting_sources_default():
async for message in client.receive_response(): async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init": if isinstance(message, SystemMessage) and message.subtype == "init":
output_style = message.data.get("output_style") output_style = message.data.get("output_style")
assert ( assert output_style != "local-test-style", (
output_style != "local-test-style" f"outputStyle should NOT be from local settings (default is no settings), got: {output_style}"
), f"outputStyle should NOT be from local settings (default is no settings), got: {output_style}" )
assert ( assert output_style == "default", (
output_style == "default" f"outputStyle should be 'default', got: {output_style}"
), f"outputStyle should be 'default', got: {output_style}" )
break break
# On Windows, wait for file handles to be released before cleanup # 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(): async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init": if isinstance(message, SystemMessage) and message.subtype == "init":
commands = message.data.get("slash_commands", []) commands = message.data.get("slash_commands", [])
assert ( assert "testcmd" not in commands, (
"testcmd" not in commands f"testcmd should NOT be available with user-only sources, got: {commands}"
), f"testcmd should NOT be available with user-only sources, got: {commands}" )
break break
# On Windows, wait for file handles to be released before cleanup # On Windows, wait for file handles to be released before cleanup
@ -159,9 +232,9 @@ async def test_setting_sources_project_included():
async for message in client.receive_response(): async for message in client.receive_response():
if isinstance(message, SystemMessage) and message.subtype == "init": if isinstance(message, SystemMessage) and message.subtype == "init":
output_style = message.data.get("output_style") output_style = message.data.get("output_style")
assert ( assert output_style == "local-test-style", (
output_style == "local-test-style" f"outputStyle should be from local settings, got: {output_style}"
), f"outputStyle should be from local settings, got: {output_style}" )
break break
# On Windows, wait for file handles to be released before cleanup # On Windows, wait for file handles to be released before cleanup

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())

View file

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

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

View file

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

View file

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

View file

@ -264,8 +264,10 @@ class ClaudeSDKClient:
async def rewind_files(self, user_message_id: str) -> None: async def rewind_files(self, user_message_id: str) -> None:
"""Rewind tracked files to their state at a specific user message. """Rewind tracked files to their state at a specific user message.
Requires file checkpointing to be enabled via the `enable_file_checkpointing` option Requires:
when creating the ClaudeSDKClient. - `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: Args:
user_message_id: UUID of the user message to rewind to. This should be user_message_id: UUID of the user message to rewind to. This should be
@ -273,11 +275,14 @@ class ClaudeSDKClient:
Example: Example:
```python ```python
options = ClaudeAgentOptions(enable_file_checkpointing=True) options = ClaudeAgentOptions(
enable_file_checkpointing=True,
extra_args={"replay-user-messages": None},
)
async with ClaudeSDKClient(options) as client: async with ClaudeSDKClient(options) as client:
await client.query("Make some changes to my files") await client.query("Make some changes to my files")
async for msg in client.receive_response(): async for msg in client.receive_response():
if isinstance(msg, UserMessage): if isinstance(msg, UserMessage) and msg.uuid:
checkpoint_id = msg.uuid # Save this for later checkpoint_id = msg.uuid # Save this for later
# Later, rewind to that point # Later, rewind to that point

View file

@ -562,6 +562,7 @@ class UserMessage:
"""User message.""" """User message."""
content: str | list[ContentBlock] content: str | list[ContentBlock]
uuid: str | None = None
parent_tool_use_id: str | None = None parent_tool_use_id: str | None = None

View file

@ -31,6 +31,21 @@ class TestMessageParser:
assert isinstance(message.content[0], TextBlock) assert isinstance(message.content[0], TextBlock)
assert message.content[0].text == "Hello" 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): def test_parse_user_message_with_tool_use(self):
"""Test parsing a user message with tool_use block.""" """Test parsing a user message with tool_use block."""
data = { data = {