# Claude Agent SDK Bugs & Workarounds

Known issues in `claude-agent-sdk` (tested against v0.1.39) and the workarounds applied by `claude-runner`.

## 1. `rate_limit_event` Kills Async Generator Chain

**Severity**: Critical

**Symptom**: "Stream ended with no content (no ResultMessage)" after retries. The session *was* running (tool calls executed, files written) but the caller never sees the result.

**Root cause**: The SDK's `parse_message()` in `_internal/message_parser.py` raises `MessageParseError` on unknown message types. When the API returns a `rate_limit_event` JSON message, this kills the async generator chain in `process_query()`. Once an async generator raises, Python marks it as exhausted — subsequent `__anext__()` calls return `StopAsyncIteration` immediately.

**Why `except/continue` doesn't work**: Python async generator semantics. When an exception propagates out of a generator's `yield`, the generator's frame is finalized. The error must be caught *inside* the generator, not by the consumer.

**Fix** (`_patch.py`): Monkey-patch `parse_message` to catch any unknown type and return a `SystemMessage` instead of raising. Must patch in **two** places because `client.py` binds the name at import time via `from .message_parser import parse_message`.

---

## 2. Environment Variable Contamination from Claude Code

**Severity**: High

**Symptom**: The `claude` CLI subprocess exits immediately with "Error: Claude Code cannot be launched inside another Claude Code session." on stderr. Since stderr is not captured by default, this appears as an empty stream.

**Root cause**: When running from within Claude Code (or tmux that inherited its env), vars like `CLAUDECODE=1` and `CLAUDE_CODE_ENTRYPOINT=cli` propagate to the `claude` CLI subprocess, which detects them and refuses to start.

**Fix** (`_env.py`): `clean_claude_env()` removes all `CLAUDE*` env vars except `ANTHROPIC_API_KEY` at program startup. The SDK itself sets `CLAUDE_CODE_ENTRYPOINT=sdk-py` when it calls `query()`, so this doesn't break the SDK protocol.

---

## 3. Silent stderr Discard

**Severity**: Medium

**Symptom**: No error output from the `claude` CLI subprocess is ever visible, even when the subprocess crashes.

**Root cause**: The SDK only pipes stderr when `ClaudeAgentOptions.stderr` is set. Otherwise `SubprocessCLITransport` sets stderr to `subprocess.DEVNULL`.

**Fix** (`_runner.py`): The `on_stderr` callback is wired to `ClaudeAgentOptions.stderr`, ensuring stderr is always captured when the caller provides a handler.

---

## Debugging Checklist

When the SDK produces "Stream ended with no content":

1. **Check stderr** — is the CLI even starting? Pass `on_stderr=print`.
2. **Check env vars** — run `env | grep CLAUDE` in the launch environment. Call `clean_claude_env()`.
3. **Check for unknown message types** — the patch should handle these, but add logging to confirm.
4. **Check the generator is alive** — if you see N messages then sudden exit, the generator was killed by an exception.
5. **Check model availability** — some models return different streaming event types.
