A guide to proxying Anthropic's Claude Code CLI through a local OmniRoute gateway. Includes an interactive fzf model switcher script to change model combos (Claude Opus, GPT-5.6, DeepSeek-v4, Kimi K3, Gemini) per session, plus a background service setup for macOS.
Claude Code connects to Anthropic by default. By redirecting ANTHROPIC_BASE_URL to OmniRoute (http://localhost:20128), request streams are proxied through OmniRoute's quota-aware fallback engine.
+-------------------+ +-----------------------+ +------------------------------------+
| claude-pick | ----> | Claude Code CLI | ----> | OmniRoute Gateway |
| (fzf selector) | | (reads settings.json)| | http://localhost:20128 |
+-------------------+ +-----------------------+ +------------------------------------+
|
v
+------------------------------------+
| Model Combos & Provider Fallbacks |
| • Claude: Opus 5 -> Sonnet 4.6 |
| • GPT 5.6: Sol -> Terra |
| • DeepSeek / Kimi / Gemini |
+------------------------------------+
- Node.js (v18+)
- OmniRoute (
npm install -g omniroute) - Claude Code CLI (
claude) - fzf (
brew install fzf)
Set ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY in your global Claude Code configuration:
{
"env": {
"ANTHROPIC_BASE_URL": "http://localhost:20128",
"ANTHROPIC_API_KEY": "YOUR_OMNIROUTE_API_KEY"
},
"enabledMcpjsonServers": [],
"skipDangerousModePermissionPrompt": true,
"theme": "dark",
"largeContextWindowFallback": true
}Important: Do not define
ANTHROPIC_MODELinsidesettings.json. Values set insettings.jsonoverride environment variables exported in the terminal, which prevents dynamic model switching via shell scripts.
Save the following script to ~/.local/bin/claude-pick and make it executable (chmod +x ~/.local/bin/claude-pick). It queries OmniRoute's endpoints, groups available combos and provider models, and opens an interactive fzf prompt before launching Claude Code.
#!/usr/bin/env bash
OMNIROUTE_URL="${OMNIROUTE_URL:-http://localhost:20128}"
OMNIROUTE_KEY="${OMNIROUTE_API_KEY:-YOUR_OMNIROUTE_API_KEY}"
FZF_BIN="$(which fzf 2>/dev/null || echo /opt/homebrew/bin/fzf)"
export ANTHROPIC_BASE_URL="$OMNIROUTE_URL"
export ANTHROPIC_API_KEY="$OMNIROUTE_KEY"
if ! curl -sf "$OMNIROUTE_URL/v1/models" -H "x-api-key: $OMNIROUTE_KEY" -o /dev/null 2>/dev/null; then
echo "OmniRoute server is down. Starting background instance..."
omniroute --no-open &
sleep 4
fi
MODEL_LIST=$(python3 - <<PYEOF
import json, subprocess, os
base = os.environ["ANTHROPIC_BASE_URL"]
key = os.environ["ANTHROPIC_API_KEY"]
def fetch(path):
r = subprocess.run(
["curl", "-sf", base + path, "-H", "x-api-key: " + key],
capture_output=True, text=True
)
return json.loads(r.stdout)
models = fetch("/v1/models")["data"]
combos = fetch("/api/v1/combos")["data"]
lines = []
lines.append("--- COMBOS (Fallback Chains) --------------------------------------------")
for c in combos:
lines.append(f"COMBO:{c['name']} · {len(c['models'])} models · {c['strategy']}")
lines.append("")
lines.append("--- AUTO-ROUTED --------------------------------------------------------")
for m in models:
if m["id"].startswith("auto/"):
lines.append(m["id"])
groups = {}
for m in models:
mid = m["id"]
if mid.startswith("auto/") or mid.startswith("no-think/"):
continue
owner = m.get("owned_by", "other")
groups.setdefault(owner, []).append(mid)
for owner in sorted(groups.keys()):
lines.append("")
lines.append(f"--- {owner.upper()} ------------------------------------------------------------")
for mid in sorted(groups[owner]):
lines.append(mid)
lines.append("")
lines.append("--- NO-THINK VARIANTS ---------------------------------------------------")
for m in models:
if m["id"].startswith("no-think/"):
lines.append(m["id"])
print("\n".join(lines))
PYEOF
)
TOTAL=$(echo "$MODEL_LIST" | grep -c "^[^─-]" || true)
SELECTED=$(echo "$MODEL_LIST" | "$FZF_BIN" \
--ansi \
--height=85% \
--layout=reverse \
--border=rounded \
--prompt="Model > " \
--header="OmniRoute Models ($TOTAL available) - Select or search" \
--no-multi \
2>/dev/null)
if [ -z "$SELECTED" ]; then
MODEL_ID="Claude"
elif [[ "$SELECTED" == COMBO:* ]]; then
MODEL_ID=$(echo "$SELECTED" | sed 's/^COMBO://' | awk -F' ·' '{print $1}' | xargs)
elif [[ "$SELECTED" == -* ]]; then
MODEL_ID="Claude"
else
MODEL_ID=$(echo "$SELECTED" | xargs)
fi
echo "Selected model: $MODEL_ID"
export ANTHROPIC_MODEL="$MODEL_ID"
export ANTHROPIC_SMALL_FAST_MODEL="Basic Tasks"
exec claude --dangerously-skip-permissionsTo ensure OmniRoute runs automatically in the background without manually launching it each session, create ~/Library/LaunchAgents/com.omniroute.server.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.omniroute.server</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/omniroute</string>
<string>--no-open</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/YOUR_USER</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>3</integer>
<key>StandardOutPath</key>
<string>/Users/YOUR_USER/.omniroute/logs/omniroute.stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/YOUR_USER/.omniroute/logs/omniroute.stderr.log</string>
<key>WorkingDirectory</key>
<string>/Users/YOUR_USER/.omniroute</string>
</dict>
</plist>Load the daemon:
mkdir -p ~/.omniroute/logs
launchctl unload ~/Library/LaunchAgents/com.omniroute.server.plist 2>/dev/null || true
launchctl load -w ~/Library/LaunchAgents/com.omniroute.server.plistNote: The --no-open flag prevents OmniRoute from launching browser tabs every time the background service starts.
-
ANTHROPIC_BASE_URLPath Format: Set the base URL tohttp://localhost:20128(or your host IP). Do not append/v1to the base URL string. Claude Code appends/v1/messagesinternally. -
Environment Variable Precedence: If
ANTHROPIC_MODELis present in~/.claude/settings.json, Claude Code enforces it for all sessions regardless of exported terminal environment variables. KeepANTHROPIC_MODELout ofsettings.jsonsoclaude-pickcan pass models dynamically. -
Authentication Key Name: Claude Code expects
ANTHROPIC_API_KEYfor API key authentication. PassingANTHROPIC_AUTH_TOKENcauses Claude Code to default to its browser-based Anthropic login flow. -
Schema Validation: In
settings.json, ensure"enabledMcpjsonServers"is set to an empty array[]rather than a boolean.
To confirm requests are routing to OmniRoute correctly:
# Check running service status
launchctl list | grep omniroute
# Check latest OmniRoute transaction logs
ls -t ~/.omniroute/call_logs/$(date +%Y-%m-%d)/ | head -1 | xargs -I {} cat ~/.omniroute/call_logs/$(date +%Y-%m-%d)/{}Check the log summary for requestedModel and actualModel to confirm the selected combo routed to the expected underlying model.