Bifurcating Claude API Authentication: Subscription UI for Interactive Work, Cost-Optimized Haiku for Programmatic Farming
Problem Statement
API token costs for Claude have become unsustainable—reaching $1500/month—while interactive development work via claude.ai requires a subscription token. The core conflict: environment variable collision between ANTHROPIC_API_KEY and OAuth-based subscription authentication means choosing one globally breaks the other. This post documents the mechanism used to achieve simultaneous support for both authentication paths within a single development environment, with programmatic work automatically farming out to a cost-optimized Claude instance running on EC2.
What Was Done
The solution implements shell-level auth scoping rather than application-level configuration, enabling:
- Interactive
claudeinvocations use the subscription token (OAuth via keychain, stored asClaude Code-credentialswith accountcb) - Programmatic/API work sources
~/repos.envto injectANTHROPIC_API_KEYonly where needed - Farm-out wrapper detects complexity and delegates to a Lightsail-hosted Claude instance running Haiku (cheaper, acceptable error rates for atomic subtasks)
- No application settings modified; no destructive changes to existing shell configuration
Technical Details: Authentication Mechanics
The critical insight is that ~/.claude/settings.json does not have a forceLoginMethod key that affects consumer-grade Claude Code (that's managed/enterprise-only and silently ignored). Instead, Claude Code's auth decision is purely environment-driven:
- If
ANTHROPIC_API_KEYis set: uses API key authentication (Claude backend, billing to account) - If
ANTHROPIC_API_KEYis absent: falls back to keychain OAuth token (Claude Code-credentials, accountcb), which is automatically populated by the subscription login flow
The previous global export in ~/.zshrc:
export ANTHROPIC_API_KEY="sk-ant-..."
…was present in the interactive shell, shadowing the keychain token and forcing all interactive claude invocations to consume API quota. The fix removes this global export entirely.
Scoped Key Injection for Programmatic Work
Existing scripts and CI workflows already follow the pattern of sourcing ~/repos.env for environment setup. This file contains:
export ANTHROPIC_API_KEY="sk-ant-..."
export DEV_TUNNEL_URL="..."
export DEV_AGENT_SECRET="..."
Scripts that require API key access now explicitly source this file:
#!/bin/bash
source ~/repos.env
claude some-complex-task
This ensures the key is only in scope where needed, not polluting the interactive shell.
Infrastructure: EC2 Farm-Out Node
A Lightsail instance serves as the cost-optimized Claude provider:
- Host:
ubuntu@34.239.233.28(Lightsail; internal IPip-172-26-6-34) - SSH Key:
~/.ssh/LightsailDefaultKey-us-west-2.pem(passwordless, verified withBatchModeSSH probe) - Claude Installation:
/usr/bin/claudepresent and functional - Service:
jada-agent.service(active systemd unit) manages the daemon
Authentication on the Farm-Out Box
Critically, the EC2 instance does not store API credentials in the login shell. Instead, jada-agent.service (the systemd service managing Claude) injects credentials per-invocation. This means:
- SSH into the box and spawn a shell: credentials are not present in that interactive session
- The farm-out wrapper (running locally) must explicitly pass
ANTHROPIC_API_KEYand model selection when invoking remote Claude over SSH - This prevents accidental credential leakage and keeps the EC2 box stateless with respect to secrets
A farm-out wrapper invokes the remote Claude like this (pseudocode; no real secrets):
ssh -i ~/.ssh/LightsailDefaultKey-us-west-2.pem ubuntu@34.239.233.28 \
"ANTHROPIC_API_KEY='${API_KEY}' /usr/bin/claude --model claude-3-5-haiku-20241022 '${PROMPT}'"
Key Architectural Decisions
1. Why Shell-Level Scoping, Not Application Config?
Claude Code's authentication layer is environment-first. Attempting to override it via ~/.claude/settings.json (e.g., with a hypothetical forceLoginMethod key) would either:
- Have no effect (ignored by consumer builds)
- Require patching the application itself
Shell environment variables are the intended control surface and require zero application changes.
2. Why Keep the API Key in repos.env Instead of a Dedicated Secret Manager?
The key was already there, following the project's existing convention. Moving it to AWS Secrets Manager or HashiCorp Vault would introduce:
- Additional CLI dependencies (aws-cli, vault)
- Latency on every script invocation (API call to fetch)
- More complex audit trails
For a developer machine with physical access controls, file-based storage with strict permissions (600) is acceptable. CI/CD systems would benefit from secret injection, but that's out of scope for this interactive+farm-out setup.
3. Why Haiku on EC2 Instead of Opus or Sonnet?
Cost optimization. Haiku is ~90% cheaper than Opus. The use case assumes work is only farmed out after being "atomized"—broken into small, well-defined subtasks that a simpler model can execute reliably. Interactive work (which requires higher accuracy and context awareness) stays on the subscription plan. This creates a two-tier system:
- Subscription (interactive): full Claude intelligence, no marginal cost per token
- API Haiku (farmed): ~1/20th the cost, acceptable for low-complexity tasks
Verification and Testing
Before finalizing, the EC2 farm-out node was verified via:
- SSH connectivity:
BatchModeprobe using the Lightsail private key succeeded without interactive auth - Claude binary: confirmed present at
/usr/bin/claudeand executable - Service status:
jada-agent.serviceis active and running