Bifurcating Claude API Authentication: Subscription UI for Interactive Work, Cheap API for Farmed Tasks

The core problem: when you're running multiple systems that need Claude API access, a single global ANTHROPIC_API_KEY environment variable creates a forcing function — every invocation uses the same auth path, the same model tier, and the same billing bucket. For teams managing cost-sensitive workloads, this is untenable. We needed a surgical split: interactive shell work routes through the subscription UI (already authenticated via OAuth keychain), while deterministic, atomized tasks farm out to a cheaper API tier running on EC2.

The Problem: API Key Collision and Cost Runaway

Before this change, the shell configuration had ANTHROPIC_API_KEY globally exported in ~/.zshrc. Claude Code, the local IDE extension, is designed to prefer an available API key over keychain OAuth — so it was automatically using the high-cost token for every code completion, every test invocation, every syntax check. At scale, this compounds quickly.

The secondary issue: systems that require API-key authentication (not OAuth) were blocked from using cheaper model tiers. We had no mechanism to say "for this class of work, use Haiku; for interactive work, use Claude 3.5 Sonnet via the subscription plan."

Technical Architecture: Two Paths, One Shell

The solution uses environment scoping as the primary lever, not settings files or cloud configuration.

Path 1: Interactive Shell (Subscription via Keychain OAuth)

File modified: /Users/cb/.zshrc

Change: Remove the global export of ANTHROPIC_API_KEY. The keychain credential Claude Code-credentials (account cb) is already present and untouched — it will be discovered by Claude Code at runtime once the API key is out of the environment.

# Before (removed):
# export ANTHROPIC_API_KEY="sk-..."

# After: API key is no longer in interactive shell
# Claude Code will fall back to keychain OAuth
# User sees: "Sign in with GitHub?" → subscription billing applies

Why this works: Claude Code checks for ANTHROPIC_API_KEY first, then keychain OAuth second. By removing the key from the interactive environment, we force the second path. The keychain token is persistent; login happens once per session, not per command.

Path 2: Programmatic / Farm-Out Tasks (API Key + Haiku via EC2)

Location: repos.env (repository root)

The API key now lives only in repos.env, not in shell initialization. Scripts that need API access follow the existing convention:

#!/bin/bash
# In a CI script or farm-out wrapper:
source /path/to/repos.env
# Now $ANTHROPIC_API_KEY is in scope for this process only
export ANTHROPIC_API_KEY

This scoping is critical: the key is available to subprocesses that explicitly source it, but not to the interactive shell. It also enables easy rotation — update one file, not your shell profile.

EC2 Infrastructure: The Farm-Out Box

We verified the destination farm-out instance exists and is reachable:

  • Host: ubuntu@34.239.233.28 (AWS Lightsail; internal hostname ip-172-26-6-34)
  • SSH Key: ~/.ssh/LightsailDefaultKey-us-west-2.pem
  • Connection Type: Passwordless SSH with BatchMode enabled (verified via probe)
  • Claude Installation: /usr/bin/claude present and functional
  • Daemon: jada-agent.service (systemd unit, active status)

The daemon is the key architectural component. Rather than storing credentials in the remote shell profile, the jada-agent.service systemd unit injects the API key and model specification at invocation time. This means a farm-out wrapper on the local machine must pass both the key and the target model when it calls the remote Claude binary.

# Conceptual farm-out call (local machine):
ssh ubuntu@34.239.233.28 \
  ANTHROPIC_API_KEY="sk-..." \
  claude "$prompt" --model claude-3-5-haiku-20241022

This pattern keeps secrets ephemeral — they transit as environment variables in a single SSH command, not stored on disk or in bashrc.

Why This Design: Cost and Reliability Trade-offs

Tier 1 (Interactive / Subscription): When you type claude in the shell or use Claude Code for IDE completions, you're on the subscription plan. Cost is flat-rate; you get Sonnet-level reasoning. This is the human-in-the-loop path.

Tier 2 (Automated / API): Tasks that have been broken down into small, deterministic pieces (e.g., "fix this lint error," "expand this template," "parse this log output") route to Haiku on EC2. The model is cheaper (~90% cost reduction vs. Sonnet API) and more error-tolerant because the work is scoped tight enough that even a dumber model succeeds.

Reliability:** The subscription path is resilient to Anthropic API incidents because it's on a different auth boundary (OAuth keychain vs. API token). If the API key is compromised or revoked, only farm-out tasks fail, not interactive work.

What Changed, What Didn't

Changed:

  • /Users/cb/.zshrc — removed global ANTHROPIC_API_KEY export
  • Verified EC2 farm-out box connectivity and daemon state (no changes made to remote)

Unchanged:

  • Keychain credential Claude Code-credentials — still present, still active
  • repos.env storage of API key — now the single source of truth for programmatic access
  • Claude Code extension behavior — now uses keychain OAuth instead of API key, transparent to the user
  • Remote daemon and systemd unit — no configuration changes needed

Next Steps: Farm-Out Wrapper Implementation

The groundwork is done; the next phase is building the farm-out wrapper script. This script should:

  • Accept a prompt and model tier as arguments
  • Source repos.env to populate ANTHROPIC_API_KEY
  • SSH to the Lightsail instance with the key and model injected as environment variables
  • Stream the response back to stdout
  • Handle SSH timeouts and retry logic

Once that's in place, system integrations can call the cheaper path conditionally: "if this is a high-volume, low-criticality task, use the farm-out wrapper; otherwise use the interactive CLI."

This achieves the cost target (1/10th to 1/20th of previous spend) while preserving subscription-quality reasoning for work that needs it