# claude-runner

Python wrapper around the [Claude Agent SDK](https://docs.anthropic.com/en/docs/claude-code/agent-sdk) with automatic retry, timeout management, and critical bug workarounds.

## Install

```bash
pip install claude-runner
```

Requires `claude-agent-sdk>=0.1.39` and Python 3.11+.

## Usage

```python
from claude_runner import run, run_sync

# Async — direct LLM call (default: tools=[], max_turns=1)
result = await run("Explain quicksort in one sentence")
print(result.text)

# Sync wrapper
result = run_sync("Explain quicksort in one sentence")

# Agentic with retry
result = await run(
    prompt="Refactor auth module",
    tools=None,           # SDK defaults (all tools)
    max_turns=None,       # SDK defaults (no limit)
    retries=5,
    retry_base_delay=10.0,
    retry_max_delay=60.0,
    retry_jitter=True,
    timeout_minutes=30,
)

if result.is_error:
    print(f"Failed: {result.error}")
else:
    print(result.text)
    print(f"Cost: ${result.cost_usd:.4f}")
```

## API

### `run(prompt, **kwargs) -> RunResult`

Async. Streams a Claude Agent SDK session. Never raises for session failures — check `result.is_error`.

| Parameter | Default | Description |
|-----------|---------|-------------|
| `model` | `None` | Model identifier (e.g. `"claude-sonnet-4-6"`) |
| `system_prompt` | `None` | System prompt |
| `max_turns` | `1` | Max turns (`None` for SDK default) |
| `tools` | `[]` | Tool names (`None` for SDK defaults) |
| `retries` | `0` | Additional attempts after first |
| `timeout_minutes` | `30` | Per-attempt timeout |
| `retry_base_delay` | `10.0` | Base delay for exponential backoff |
| `retry_max_delay` | `None` | Cap on computed delay |
| `retry_jitter` | `False` | Randomize delay by +/-25% |
| `on_text` | `None` | Callback for each text block |
| `on_stderr` | `None` | Callback for CLI stderr |
| `options` | `None` | Full `ClaudeAgentOptions` escape hatch |

### `run_sync(prompt, **kwargs) -> RunResult`

Synchronous wrapper. Calls `asyncio.run(run(...))`. Cannot be called from a running event loop.

### `RunResult`

Dataclass with: `text`, `cost_usd`, `usage`, `duration_ms`, `num_turns`, `session_id`, `is_error`, `error`, `result_message`, `messages`.

### `clean_claude_env()`

Strips `CLAUDE*` env vars (except `ANTHROPIC_API_KEY`) to prevent subprocess contamination when running from within Claude Code.

## Bug workarounds

Automatically applied at import time:

1. **Rate limit event kills generator** — SDK's `parse_message()` raises on unknown message types. Patched to return `SystemMessage` instead.
2. **Env var contamination** — `CLAUDECODE=1` inherited from Claude Code breaks the CLI subprocess. `clean_claude_env()` strips it.
3. **Silent stderr discard** — SDK sends stderr to `/dev/null`. The `on_stderr` parameter wires through automatically.

## License

MIT
