```html

Bifurcating Claude API Access: Subscription UI for Interactive Work, Cost-Optimized API for Programmatic Tasks

The Problem: Cost Spiral and Auth Conflicts

When you're running systems that depend on Claude API across multiple environments—interactive development, automated scripts, CI/CD pipelines—you face a hard constraint: a single global API key in your shell environment forces everything through the same billing tier. At $1500/month in token spend, that's unsustainable. Meanwhile, interactive development work (which is higher-touch, lower-volume) should use the Claude subscription web UI—cheaper per-interaction, user-friendly, and already paid for. The tension is real: you can't afford to run all API calls through the subscription plan, but you also can't let low-stakes programmatic work break high-stakes interactive sessions by burning through quota.

The secondary issue: claude.ai (subscription token via keychain OAuth) and ANTHROPIC_API_KEY (API billing) both exist in your environment, and Claude Code can't cleanly switch between them when the key is globally exported in ~/.zshrc.

Technical Diagnosis

The solution isn't in Claude Code settings (the forceLoginMethod key is enterprise-only and silently ignored in ~/.claude/settings.json). It's purely shell-environment scoping.

Current state:

  • ANTHROPIC_API_KEY exported globally in ~/.zshrc (sourced from ~/.config/repos.env)
  • Claude Code keychain OAuth token stored at ~/.claude/credentials/Claude Code-credentials (account cb), already configured and untouched
  • EC2 Lightsail box (ubuntu@34.239.233.28, internal IP ip-172-26-6-34) with claude CLI installed and jada-agent.service active
  • SSH access via ~/.ssh/LightsailDefaultKey-us-west-2.pem (passwordless, verified with BatchMode probe)
  • The EC2 Claude daemon injects API credentials per-invocation, not in the login shell

Architecture: Dual-Path Invocation

The fix creates two distinct code paths, each optimized for its use case:

Path 1: Interactive `claude` → Subscription Web UI

When you type claude in an interactive shell, it invokes the claude.ai web UI (or Claude Code) using your keychain OAuth token. The shell environment must not have ANTHROPIC_API_KEY set at invocation time. Once the global export is removed from ~/.zshrc, Claude Code's credential chain falls back to the keychain OAuth token stored at ~/.claude/credentials/Claude Code-credentials.

Path 2: Programmatic & Farm-Out → API Key + Cheapest Model

Scripts, CI/CD, and farm-out wrappers that need the API key source ~/.config/repos.env explicitly. This keeps the key scoped to subprocess environments only, never polluting the global interactive shell. Farm-out work (broken into granular, low-risk tasks) runs on the EC2 box via the jada-agent.service daemon, which injects the API key and routes to the cheapest viable Claude model (e.g., Haiku for well-scoped subtasks).

Implementation Details

1. Remove Global API Key Export

In ~/.zshrc, delete or comment out:

export ANTHROPIC_API_KEY=$(cat ~/.config/repos.env | grep ANTHROPIC_API_KEY | cut -d'=' -f2)

Why: This forces interactive shells to rely on Claude Code's keychain OAuth, which is already configured and cheaper per-interaction.

2. Establish the Farm-Out Wrapper Convention

Any script that needs the API key must explicitly source ~/.config/repos.env at the start:

#!/bin/bash
set -e

# Load API credentials for this subprocess only
source ~/.config/repos.env

# Now $ANTHROPIC_API_KEY is set; child processes inherit it
# Invoke cheap Claude on EC2 for a granular subtask
ssh -i ~/.ssh/LightsailDefaultKey-us-west-2.pem ubuntu@34.239.233.28 \
  /usr/local/bin/claude-cheap-invoke --model haiku --task "parse_json_output"

Why: Subprocess scoping avoids polluting the parent shell. The EC2 box then handles routing to the optimal (cheapest, fastest-failing) model for that task.

3. EC2 Daemon: jada-agent.service

The Lightsail box already runs an active systemd service at /etc/systemd/system/jada-agent.service. This daemon:

  • Listens for SSH invocations from your workstation
  • Accepts task metadata (model, prompt, task ID)
  • Injects ANTHROPIC_API_KEY from secure storage (e.g., an encrypted env file or systemd secret) before each Claude invocation
  • Routes to the cheapest model that can safely handle the task
  • Returns results via stdout/stderr or S3 (for large outputs)

Key file paths on the EC2 box:

  • /usr/bin/claude — Claude CLI binary (confirmed present)
  • /etc/systemd/system/jada-agent.service — Service unit (active and verified)
  • /var/log/jada-agent.log — Service logs (for debugging farm-out failures)
  • ~ubuntu/.config/repos.env — API key and model preferences (injected by the service, not the login shell)

4. SSH & Batching

Farm-out invocations use SSH in BatchMode (no interactive prompts), with the Lightsail key:

ssh -i ~/.ssh/LightsailDefaultKey-us-west-2.pem \
    -o BatchMode=yes \
    -o StrictHostKeyChecking=accept-new \
    ubuntu@34.239.233.28 \
    /usr/local/bin/invoke-claude-task --model haiku --input "$TASK_JSON"

This ensures that high-latency network calls don't block your interactive session; farm-out work is fire-and-forget or polled asynchronously.

Key Decisions & Trade-Offs

  • Why not use `forceLoginMethod` in settings.json? — It's enterprise/managed-Claude only; silently ignored in personal Claude Code. The shell-environment approach is the only reliable lever.
  • Why source `~/.config/repos.env` in scripts instead of exporting it globally? — Subprocess scoping keeps the API key out of your interactive prompt history and out of child processes that don't need it (reducing blast radius if a