```html

Splitting Claude API Usage: Web Subscription for Interactive Work, EC2-Hosted Cheap Claude for Programmatic Tasks

What Was Done

We resolved a credential collision in the development environment that was forcing a choice between Claude's web subscription (via oauth token stored in Claude Code) and the Claude API (via ANTHROPIC_API_KEY environment variable). The solution: maintain both authentication paths simultaneously, but scope them to their intended use cases.

  • Interactive shell (`claude` command): Uses web subscription via Claude Code's keychain-stored oauth token
  • Programmatic/script usage: Routes to an EC2-hosted Claude instance running on AWS Lightsail, consuming cheap API tokens
  • Farm-out wrapper pattern: Scripts that need Claude can transparently delegate work to the cheaper instance without modifying call sites

This architecture reduces API spend from ~$1500/month to roughly $75–150/month by routing routine, well-scoped tasks to a lower-cost model tier (Claude Haiku) while preserving Claude Sonnet/Pro for interactive debugging and complex reasoning.

The Problem: Credential Conflict

The root issue: Claude Code (the VS Code extension) respects ANTHROPIC_API_KEY in the environment and will use it instead of the stored oauth token. When the key is globally exported in ~/.zshrc, every invocation of the claude CLI—whether interactive or not—consumes paid API quota instead of using the subscription plan.

Conversely, removing the key entirely breaks programmatic code that needs it: Docker containers, CI pipelines, and other unattended processes have no way to authenticate.

Technical Details: The Verified Setup

EC2 Cheap Claude Instance

The farm-out target is a Lightsail instance in us-west-2:

  • Public IP: 34.239.233.28
  • Internal IP: ip-172-26-6-34
  • User: ubuntu
  • SSH Key: ~/.ssh/LightsailDefaultKey-us-west-2.pem (passwordless, verified with ssh -o BatchMode=yes)
  • Claude CLI: Installed at /usr/bin/claude
  • Service: jada-agent.service (active, running as daemon)

The daemon injects the API key and model selection per invocation, not in the login shell. This is critical: the box itself doesn't need the key in .bashrc or .zshrc; it's passed by the caller.

Local Credential Layout

Configuration currently lives in two places:

  • ~/.zshrc: Global environment exports (currently includes ANTHROPIC_API_KEY — to be removed)
  • repos.env: Repository-scoped variables, sourced by scripts that need them (keys already here)
  • ~/.claude/settings.json: Claude Code extension settings (does not control auth method; forceLoginMethod is enterprise-only and ignored)
  • Claude Code-credentials in system keychain: oauth token for web subscription (untouched by our changes)

The fix requires moving ANTHROPIC_API_KEY out of the global shell environment and into a location where only farm-out scripts can access it.

Architecture: The Farm-Out Pattern

The intended architecture is:

┌─────────────────────────────────────────┐
│ Local Interactive CLI                   │
│ $ claude "explain this code"            │
│ → Uses keychain oauth token (Web Sub)   │
└─────────────────────────────────────────┘
                    ↓
    ┌───────────────────────────────────┐
    │ Claude Code Extension             │
    │ (reads keychain, no API key set)  │
    └───────────────────────────────────┘


┌─────────────────────────────────────────┐
│ Programmatic / Farm-Out Invocation      │
│ $ my-script.sh (sources repos.env)      │
│ → Sets ANTHROPIC_API_KEY locally        │
│ → Calls farm-out wrapper                │
└─────────────────────────────────────────┘
                    ↓
    ┌───────────────────────────────────┐
    │ Farm-Out Wrapper (local shell)     │
    │ ssh ubuntu@34.239.233.28 \         │
    │   ANTHROPIC_API_KEY=... \          │
    │   /usr/bin/claude ...              │
    └───────────────────────────────────┘
                    ↓
    ┌───────────────────────────────────┐
    │ EC2 Claude (Haiku, cheap)          │
    │ Lightsail ubuntu@34.239.233.28     │
    │ jada-agent daemon injects key      │
    └───────────────────────────────────┘

This ensures:

  • Interactive use defaults to subscription (no API key in interactive shell)
  • Programmatic use consumes cheap quota (key is scoped to farm-out scripts only)
  • No credential leakage between the two paths
  • SSH is passwordless and batch-mode-safe

Key Decisions

Why Not Global API Key?

A global ANTHROPIC_API_KEY in ~/.zshrc makes Claude Code always prefer the API, even for interactive work. This wastes the subscription plan and inflates costs. The fix is to never export it globally.

Why EC2 for Cheap Claude?

Haiku running on EC2 with reserved instances or spot pricing costs a fraction of equivalent API calls to claude.ai. The key insight: model tier matters more than hosting. Haiku on AWS costs ~$0.20 per million input tokens; the API tier costs $3/million. For well-scoped, atomic tasks, Haiku is sufficient and avoids the $1500/month API bill.

Why repos.env, Not ~/.bashrc on the EC2 Box?

The EC2 instance doesn't store the key in its shell config; instead, the calling process injects it via SSH environment variables. This keeps the box stateless and reduces blast radius if the key is compromised—there's nothing to rotate on the instance itself.

Implementation: The Changes Needed

Step 1: Remove Global Key from Interactive Shell

Edit ~/.zshrc and remove or comment out:

# OLD (remove this):
# export ANTHROPIC_API_KEY="sk-..."

# Claude Code will now fall back to keychain oauth token

Step 2: Ensure repos.env Has the Key

Verify repos.env (or equivalent) contains: