```html

Splitting Claude API Authentication: Subscription UI for Interactive Work, Budget API for Farmed Tasks

What Was Done

We diagnosed and resolved an authentication conflict where a single shell environment couldn't cleanly separate interactive Claude.ai subscription usage from programmatic API-key-based calls. The solution: partition authentication at the shell-environment level, allowing interactive claude invocations to use the subscription keychain token (via Claude Code's OAuth flow), while programmatic and farm-out work sources the API key only when needed from repos.env.

This eliminates the need to toggle between login states and lets us route computationally trivial work to a budget Haiku instance on EC2 without bleeding subscription token usage across both channels.

The Problem

The conflict was straightforward: a global ANTHROPIC_API_KEY export in ~/.zshrc meant that every invocation of the claude command—whether interactive or scripted—would use the API key, bypassing the subscription OAuth token stored in the Claude Code keychain (~/.claude/credentials.json, account cb). Switching between modes required manual logout/login cycles, and critically, all API calls were burning through a single token budget, mixing high-value subscription work with low-value farm-out tasks.

The cost issue was severe: at prior spending rates (~$1500/month on API tokens), there was no isolation between interactive (high-quality, subscription-billed) and programmatic (low-quality, budget API) workloads.

Why the Settings Lever Didn't Work

Initial investigation checked ~/.claude/settings.json for a forceLoginMethod key. This key exists in enterprise/managed Claude Code deployments but is silently ignored in personal installations—it's not a control surface for toggling auth modes. The actual lever is purely shell environment.

Technical Architecture

Authentication Flow Before

  • Global ANTHROPIC_API_KEY set in ~/.zshrc via export
  • claude CLI checks for ANTHROPIC_API_KEY in environment
  • If present, uses API key; keychain OAuth token never consulted
  • All work—interactive and programmatic—burns API token quota

Authentication Flow After

  • ANTHROPIC_API_KEY removed from ~/.zshrc global exports
  • Interactive claude invocations → Claude Code reads keychain (~/.claude/credentials.json) → OAuth subscription token used
  • Programmatic scripts that need the API key explicitly source repos.env before invocation (existing pattern)
  • Farm-out wrapper on EC2 receives API key as parameter, injects it per-call to the remote /usr/bin/claude

EC2 Farm-Out Infrastructure (Verified)

Host Details:

  • Public IP: 34.239.233.28 (Lightsail, us-west-2 region)
  • Internal IP: ip-172-26-6-34
  • OS: Ubuntu (verified passwordless SSH via ~/.ssh/LightsailDefaultKey-us-west-2.pem)
  • SSH Test: ssh -i ~/.ssh/LightsailDefaultKey-us-west-2.pem ubuntu@34.239.233.28 'echo ok' succeeds in BatchMode (no interactive prompt)

Claude on Box:

  • /usr/bin/claude is present and functional
  • jada-agent.service (systemd unit) is active and manages the daemon
  • The daemon accepts API key and model selection as per-invocation parameters, not via shell exports on the remote box

This is critical: unlike the local environment where Claude Code reads the keychain, the EC2 instance is stateless with respect to auth. Each farm-out call must carry its own credentials.

Key Decisions

Why Not Move the Key to a Secrets Manager?

The key already lives in repos.env and is source-controlled (in a private repo). AWS Secrets Manager or similar would add operational overhead (additional IAM roles, rotation complexity) for a marginal security gain in this context. The sensitivity is cost control, not breach prevention—farm-out work is low-stakes. Keeping the key in repos.env maintains the existing convention and makes the farm-out wrapper trivial to wire.

Why Remove the Global Export Instead of Conditional Logic?

Conditional exports (e.g., if [[ -z $CLAUDE_MODE ]] then export ANTHROPIC_API_KEY...) require discipline to set environment flags before every invocation. A simpler model: absence of the key in the interactive shell is the default state. Code that needs it pulls it explicitly. This is fail-safe: if you forget to source repos.env, you get a clean error ("API key not found") rather than silent cross-contamination.

Why Haiku for Farm-Out?

Haiku is the cheapest available Claude model and, in the original design intent, suitable for "atomized" tasks that have been sufficiently decomposed. The architecture allows routing only trivial subtasks (e.g., "format this JSON", "lint this Python") to the EC2 instance while keeping complex reasoning on the subscription tier. This keeps costs predictable and quality high.

Implementation Pattern

Step 1: Remove Global Export

Edit ~/.zshrc and remove the line:

export ANTHROPIC_API_KEY=...  # DELETE THIS

Step 2: Create Farm-Out Wrapper

A simple wrapper script (e.g., ~/bin/claude-farm) reads the key from repos.env and dispatches to the EC2 instance:

#!/bin/bash
# Source the key locally (not exported to environment)
source "${HOME}/path/to/repos.env"

# SSH to EC2 and invoke claude with key injected
ssh -i ~/.ssh/LightsailDefaultKey-us-west-2.pem \
  ubuntu@34.239.233.28 \
  "ANTHROPIC_API_KEY='${ANTHROPIC_API_KEY}' /usr/bin/claude $@"

Step 3: Update Programmatic Callers

Any script that needs the API key locally does:

source repos.env
# Now ANTHROPIC_API_KEY is available
claude api-call-here

Step 4: Verify Interactive Mode

After removing the export, test interactive claude:

unset ANTHROPIC_API_KEY
claude "what is 2+2?"