# Claude Code Router — Multi-Provider Backend for Claude Code

claude-code-router (CCR) is a transparent proxy that lets Claude Code's agent harness run with **any LLM backend** instead of Anthropic's models. Claude Code thinks it's talking to Anthropic — CCR intercepts and routes to your chosen provider.

Repo: https://github.com/musistudio/claude-code-router (~34k stars, TypeScript, actively maintained)

## Architecture

```
Claude Code (Harness) → CCR (Proxy :3456) → Your Model (OpenAI-compatible API)
       ↓                        ↓
  Tool-Use, Agent-Loop    Transformer: Anthropic-Stream ↔ OpenAI-Stream
  Subagents, Permissions  Router: task-type → best model
```

Claude Code is none the wiser. The full agent experience (tool-use, subagents, plan mode, background tasks) works with any OpenAI-compatible endpoint.

## Installation

Requires Node.js ≥18 and Claude Code already installed.

```bash
npm install -g @musistudio/claude-code-router
```

Verify: `ccr --version`

## Router Concept — Task-Type → Model Mapping

CCR can route different Claude Code task types to different models:

| Router Key | Claude Code Feature | Best Model Strategy |
|---|---|---|
| `default` | General coding tasks | Fast, capable model |
| `think` | Plan Mode / Reasoning | Strongest reasoning model |
| `background` | Background tasks | Cheap/fast model |
| `longContext` | Sessions > threshold tokens | High-context-window model |
| `webSearch` | Web search tasks | Model with search support |
| `image` | Image-related tasks | Vision-capable model |

Dynamic switching in-session: `/model provider_name,model_name`

## Configuration (`~/.claude-code-router/config.json`)

### Minimal Example (Ollama Cloud)

```json
{
  "Providers": [{
    "name": "ollama",
    "api_base_url": "https://ollama.com/v1/chat/completions",
    "api_key": "your-api-key",
    "models": ["deepseek-v4-pro", "kimi-k2.6", "gemma3:4b"],
    "transformer": {
      "use": ["deepseek"],
      "deepseek-v4-pro": {
        "use": [["maxtoken", {"max_tokens": 65536}]]
      }
    }
  }],
  "Router": {
    "default": "ollama,deepseek-v4-pro",
    "think": "ollama,kimi-k2.6",
    "background": "ollama,gemma3:4b",
    "longContext": "ollama,kimi-k2.6"
  }
}
```

### Key Config Sections

- **`Providers[]`** — Each provider: name, api_base_url, api_key, models, optional transformer
- **`Router`** — Maps task types to `provider,model`
- **`APIKEY`** (optional) — If set, clients must provide Authorization header. Without it, HOST is forced to 127.0.0.1.
- **`HOST`** (optional) — Set to `"0.0.0.0"` for LAN access (requires APIKEY set)
- **`API_TIMEOUT_MS`** — API call timeout (default: 600000)
- **`NON_INTERACTIVE_MODE`** — Set true for CI/GitHub Actions/Docker

### Available Transformers

Transformers adapt Anthropic's API format ↔ provider format:
- `deepseek` — DeepSeek/Ollama/OpenAI-compatible APIs
- `gemini` — Google Gemini API
- `openrouter` — OpenRouter (with provider routing support)
- `groq` — Groq API
- `anthropic` — Direct Anthropic (passthrough)
- `maxtoken` — Cap `max_tokens` (accepts options: `{"max_tokens": 16384}`)
- `tooluse` — Optimize tool_choice for certain models
- `reasoning` — Handle `reasoning_content` field
- `sampling` — Handle temperature/top_p/top_k/repetition_penalty
- `enhancetool` — Error tolerance for tool call params (disables streaming tool calls)
- `cleancache` — Remove `cache_control` from requests

### Environment Variable Interpolation

Use `$VAR` or `${VAR}` in config for secrets:
```json
{ "Providers": [{"api_key": "$OPENAI_API_KEY"}] }
```

## Running Claude Code with CCR

### Method 1: `ccr code` (simplest)

```bash
ccr code                    # Interactive session
ccr code -p "Fix the bug"   # Print mode (one-shot)
```

### Method 2: `ccr activate` (global override)

```bash
eval "$(ccr activate)"      # Sets ANTHROPIC_BASE_URL etc.
claude                      # Now routes through CCR
```

This sets: `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL=http://127.0.0.1:3456`, `NO_PROXY=127.0.0.1`

### Method 3: Manual env vars

```bash
ANTHROPIC_BASE_URL="http://127.0.0.1:3456" ANTHROPIC_AUTH_KEY="any" claude -p "task"
```

⚠️ This may trigger Claude Code's OAuth login check. Use `ccr code` instead.

## Subagent Routing

Prefix subagent prompts with a model directive:
```
<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>
Your prompt here...
```

## ZimaOS / Read-Only Filesystem Setup

ZimaOS has `/DATA` as `$HOME` but it's read-only for non-root users. Workarounds:

1. **Node.js**: Install to `/DATA/.local/nodejs/` (download tarball, extract, add to PATH)
2. **npm**: Set cache/prefix to writable paths via env vars:
   ```bash
   export npm_config_cache="/DATA/.local/npm-cache"
   export npm_config_prefix="/DATA/.local/npm-global"
   ```
3. **CCR config location**: CCR reads from `~/.claude-code-router/`. Override HOME:
   ```bash
   mkdir -p /DATA/.local/.claude-code-router
   cp config.json /DATA/.local/.claude-code-router/
   HOME=/DATA/.local ccr start
   ```
4. **Systemd service**: Use `Environment="HOME=/DATA/.local"` in the unit file
5. **Interactive env**: Source `/DATA/.local/hermes-env.sh` for PATH + aliases

### Systemd Service Template

```ini
[Unit]
Description=Claude Code Router
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=az-a
Group=samba
Environment="PATH=/DATA/.local/nodejs/bin:/DATA/.local/npm-global/bin:/usr/bin:/bin"
Environment="HOME=/DATA/.local"
Environment="npm_config_cache=/DATA/.local/npm-cache"
Environment="npm_config_prefix=/DATA/.local/npm-global"
ExecStart=/DATA/.local/npm-global/bin/ccr start
ExecStop=/DATA/.local/npm-global/bin/ccr stop
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
```

## CLI Reference

| Command | Purpose |
|---------|---------|
| `ccr start` | Start the proxy server (background) |
| `ccr stop` | Stop the server |
| `ccr restart` | Restart (pick up config changes) |
| `ccr status` | Show server status |
| `ccr code` | Launch Claude Code through the router |
| `ccr model` | Interactive model picker |
| `ccr ui` | Web-based config editor |
| `ccr activate` | Output shell env vars for integration |
| `ccr preset export/install/list` | Manage config presets |

## Verification

After starting, check health:
```bash
curl http://127.0.0.1:3456/health   # → {"status":"ok",...}
```

And check logs for model identity:
```bash
grep '"model"' ~/.claude-code-router/logs/ccr-*.log
# Should show your configured model name, not "claude"
```

## CCR v2.0.0 — Known Bug with Ollama Cloud Pro

**CCR v2.0.0 has a confirmed "Provider 'undefined' not found" bug** (`code: provider_not_found`) when routing to Ollama Cloud Pro (`https://ollama.com/v1`). The bug persists regardless of:

- Config format (api_base_url with or without `/chat/completions`)
- Model name format (`deepseek-v4-pro` vs `ollama,deepseek-v4-pro`)
- Clean reinstall with fresh config
- Direct Node execution vs systemd service

**Evidence:** Health endpoint returns OK, process starts cleanly, but any `/v1/chat/completions` request returns `provider_not_found`. The provider is registered in config but the router internally resolves it to `undefined`.

**Workaround — Use CCR v1.x:** CCR v1.x (v1.2.3 and earlier) does NOT have this bug. Install with `npm install -g @musistudio/claude-code-router@1.2.3` (adjust version as needed). CCR v1.x successfully routes Claude Code requests to Ollama Cloud Pro.

## Pitfalls

- **Ollama Cloud Pro limits 3 concurrent models.** If Hermes Gateway is using deepseek-v4-pro for an active chat session, CCR may silently fall back to another configured model (e.g. kimi-k2.6) rather than failing. This is a feature, not a bug — coding still works, just with a different brain. To force a specific model for CCR, ensure it isn't consumed by concurrent Hermes usage, or add a 4th model slot exclusively for CCR (Ollama Cloud will 404, so instead accept the graceful fallback).
- **Claude Code's identity lies** — it always says "I'm Claude" regardless of which model answers. Check CCR logs (`grep '"model"' ~/.claude-code-router/logs/ccr-*.log`) to see the actual model responding.
- **Config changes require restart:** `ccr restart`, not `ccr start` (start won't re-read config if already running).
- **npm permission errors on ZimaOS:** `/DATA/.npmrc` is owned by root. Use env vars (`npm_config_cache` + `npm_config_prefix`) instead of `npm config set`.
- **External directories need `--add-dir` + `--permission-mode bypassPermissions`** — even in print mode, `ccr code -p` prompts for filesystem permissions when accessing paths outside CWD. Combine both flags: `ccr code --add-dir /path --permission-mode bypassPermissions -p "task"`.
- **Headless/background runs need `TERM=dumb`** — without it, tput errors ("No value for $TERM and no -T specified") pollute output. Set `TERM=dumb` in the environment before invoking `ccr code`.
- **Background processes need explicit stdin redirect** — `ccr code -p` in a background process may warn "no stdin data received, proceeding without it" and skip the prompt. Always redirect stdin: `ccr code -p "task" < /dev/null`.
- **`delegate_task` can't find `claude`** even when CCR is running — the subprocess inherits Hermes' PATH, not your custom Node.js PATH. To use CCR from within Hermes' agent loop, either: (a) symlink `claude` and `ccr` into `/usr/bin/`, or (b) use `terminal` directly with the full PATH export + HOME override.\n  - **On ZimaOS `/usr/bin` and `/usr/local/bin` are read-only.** Use `/DATA/bin/` instead (writable with sudo). Symlink all five binaries there:\n    ```bash\n    sudo ln -sf /DATA/.local/nodejs/bin/node /DATA/bin/node\n    sudo ln -sf /DATA/.local/nodejs/bin/npm /DATA/bin/npm\n    sudo ln -sf /DATA/.local/nodejs/bin/npx /DATA/bin/npx\n    sudo ln -sf /DATA/.local/npm-global/bin/ccr /DATA/bin/ccr\n    sudo ln -sf /DATA/.local/npm-global/bin/claude /DATA/bin/claude\n    ```\n  - After symlinking, `PATH` resolves all five automatically — no `HOME` override needed for `terminal` calls. Verify: `ccr code -p \"hi\" --max-turns 1 < /dev/null`.

Claude Code itself is **free** (Apache 2.0). You only pay for the LLM API calls through your provider. With Ollama Cloud Pro ($20/mo): unlimited usage within plan limits, zero Anthropic costs.
