A transparent proxy that sits between Claude Code and the Anthropic API. It adds LaunchDarkly AI Config capabilities as a sidecar: model selection, metrics tracking, and automated response quality scoring. All without changing how Claude Code behaves.
Claude Code → proxy (localhost:9911) → Anthropic API
│
├─ evaluates LD AI Config (in-memory, <1ms)
├─ injects system prompt from the config
├─ creates a run tracker
├─ streams response through unchanged
├─ parses SSE to extract tokens / stop reason / tools
├─ records metrics to LD Monitoring dashboard
└─ fires attached judges asynchronously
Everything Claude Code sends is forwarded byte-for-byte — streaming, tool use, thinking blocks, betas, and every content type pass through unchanged. LaunchDarkly is layered on without touching the response.
| Metric | LD event key |
|---|---|
| Duration | $ld:ai:duration:total |
| Input tokens (incl. cache) | $ld:ai:tokens:input |
| Output tokens | $ld:ai:tokens:output |
| Total tokens | $ld:ai:tokens:total |
| Successful generation | $ld:ai:generation:success |
| Failed generation | $ld:ai:generation:error |
| Tool calls (one per tool) | $ld:ai:tool_call |
| Judge scores | configured metric key |
- Python 3.13+
- uv (recommended) or pip
- A LaunchDarkly account with AI Configs enabled
- An Anthropic API key (passed through from Claude Code)
In the LaunchDarkly dashboard, go to AI Configs and create a new config.
- Key: use
model-selectoror setLD_AI_CONFIG_KEYto match whatever key you choose - Messages: add a
systemrole message with any instructions you want injected into every Claude Code request - Enable the config for your target environment
The proxy evaluates this config on every request. When disabled or when no system messages are present, requests are forwarded unchanged.
Judges are separate AI Configs with mode: judge that score each response on a 0–1 scale. Attach them to your AI Config in the LaunchDarkly UI under Judges.
Each judge:
- Receives the full conversation (system prompt + messages) and Claude's response text
- Returns a numeric score emitted under its configured Evaluation Metric Key
- Runs asynchronously after the response is returned to Claude Code (no added latency)
- Respects its configured sampling rate — set this below 1.0 to control cost
Judges can use any provider supported by the LD AI SDK (Anthropic, OpenAI). Use a separate API key or provider from Claude Code's own key to avoid competing for the same rate limits.
git clone <repo-url>
cd claude-code-ld-proxy
# Install dependencies
uv sync
# Configure environment
cp .env.example .env
# Edit .env and fill in LD_SDK_KEY and LD_AI_CONFIG_KEYuv run main.pyThen point Claude Code at the proxy instead of Anthropic directly:
ANTHROPIC_BASE_URL=http://localhost:9911 claudeOr set it permanently in your shell profile:
export ANTHROPIC_BASE_URL=http://localhost:9911To pass a user identity for LaunchDarkly targeting rules, include the x-ld-user-key header in your requests. Claude Code doesn't set this by default, so it falls back to claude-code-local.
| Variable | Required | Default | Description |
|---|---|---|---|
LD_SDK_KEY |
Yes | — | LaunchDarkly server-side SDK key |
LD_AI_CONFIG_KEY |
Yes | model-selector |
Key of the AI Config to evaluate |
ANTHROPIC_API_KEY |
Yes* | — | Forwarded from Claude Code automatically |
OPENAI_API_KEY |
No | — | Required if any judges use provider: openai |
PORT |
No | 9911 |
Port the proxy listens on |
PROXY_UPSTREAM |
No | https://api.anthropic.com |
Upstream API base URL |
LOG_LEVEL |
No | INFO |
DEBUG | INFO | WARNING | ERROR |
*ANTHROPIC_API_KEY is forwarded automatically from the x-api-key header that Claude Code sends. You only need it in .env if you want the proxy itself to make API calls (e.g. for judges using Anthropic as the judge provider).
Each request produces two lines (three if judges are attached):
INFO server.proxy ▶ model=claude-opus-4-8 user=claude-code-local stream=yes
INFO pipeline.tracking ◀ 1243ms in=4521 out=312 cached=801 stop=end_turn tools=read_file,bash
INFO pipeline.tracking ⚖ judge=quality-judge score=0.87 success=yes
Errors show the Anthropic error type and message:
WARNING server.proxy upstream 429 path=v1/messages type=rate_limit_error message=...
main.py # Entrypoint — runs uvicorn
core/
settings.py # Env config and shared constants
launchdarkly/
client.py # LD client init and AI Config evaluation
pipeline/
interceptor.py # Request mutation: injects system prompt, starts tracker
tracking.py # Response parsing: records metrics, runs judges
server/
proxy.py # FastAPI app and catch-all proxy route
The two entry points for customization are both in pipeline/interceptor.py:
intercept(body, user_key) — runs before the request is forwarded. Mutate body here to change the model, inject additional context, or add tools. The body dict has model, system, messages, tools, and stream.
append_system(body, extra) — convenience helper that appends text to the system prompt regardless of whether it's a string or a content-block list.
Judge scoring and metrics recording are fully automatic once judges are attached to the AI Config in the LaunchDarkly UI — no code changes needed.
GET http://localhost:9911/health
→ {"status": "ok", "launchdarkly": true}