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

The Problem: API Token Spend at Scale

When you have multiple systems invoking Claude programmatically—infrastructure tooling, batch processing, internal agents—the per-token cost compounds quickly. At $1500/month in API spend, the math becomes unavoidable: you need a two-tier strategy. Use the subscription plan (fixed cost) for interactive, high-quality work where you control the scope. Route programmatic tasks to a cheaper inference tier running on your own infrastructure.

The catch: your ~/.zshrc exports a single ANTHROPIC_API_KEY globally, which forces all Claude invocations—whether interactive or scripted—through the same paid API endpoint. The shell can't distinguish intent. This post documents the exact mechanism to separate them.

What Was Done

  • Diagnosed the auth conflict: Confirmed that ANTHROPIC_API_KEY in the interactive shell environment overrides the keychain OAuth token that Claude Code uses for subscription billing.
  • Located the EC2 farm-out box: Verified connectivity and daemon status on the Lightsail instance running a local Claude service.
  • Mapped the credential flow: Traced how the shell exports, settings files, and remote daemons each handle authentication.
  • Designed the split: Planned removal of ANTHROPIC_API_KEY from global shell exports while keeping it available to scripts that explicitly source it.

Technical Architecture: The Two-Path Model

Path 1: Interactive Claude (Subscription)

When you type claude at an interactive prompt, the shell should invoke the Claude web interface using OAuth credentials stored in your keychain. These credentials are already cached locally by Claude Code under the account label cb in the macOS credential store.

Current mechanism: Claude Code stores OAuth tokens in ~/Library/Keychains/login.keychain-db (macOS) with the account identifier Claude Code-credentials. When ANTHROPIC_API_KEY is absent from the environment, the CLI tool falls back to this keychain lookup, which authenticates against your subscription plan.

Why this works: The subscription plan is tied to your claude.ai account, not API credentials. OAuth tokens are independent of API keys. By removing the API key from the interactive shell, you force the CLI to use the keychain—which is exactly what you want for interactive, human-driven work.

Path 2: Programmatic Work (EC2 Farm-Out)

For scripts, infrastructure code, and batch tasks, you want to route work to a local Claude inference service running on EC2. This service accepts API calls, but you control which model and which parameters it uses—allowing you to trade latency/quality for cost.

The EC2 instance:

  • Host: ubuntu@34.239.233.28 (Lightsail, us-west-2; internal hostname ip-172-26-6-34)
  • SSH key: ~/.ssh/LightsailDefaultKey-us-west-2.pem (passwordless; verified with ssh -o BatchMode=yes probe)
  • Service: jada-agent.service (active systemd unit)
  • Claude binary: /usr/bin/claude (present on box; invoked by daemon)

How the daemon works: The systemd unit reads configuration from an EnvironmentFile directive that injects the API key and model selection at invocation time. This means the SSH session itself doesn't need to know credentials—the remote daemon owns them.

Key Decisions

Why Remove the Global Export, Not Add Conditionals

You might think: "Add an alias that checks if we're in a script context." Resist this. Shell context detection is fragile (piped input? subshell? non-interactive batch mode?). Instead, invert the problem: scripts that need the API key explicitly source repos.env (your existing convention). This is auditable, explicit, and doesn't rely on heuristics.

File: /Users/cb/.zshrc

The modification removes:

export ANTHROPIC_API_KEY="..."

And replaces it with a comment explaining the two-path model. Scripts that need it can source it locally:

# In a script or sourced context:
source ~/path/to/repos.env
claude --api-key "$ANTHROPIC_API_KEY" ...

Why SSH + Remote Daemon, Not a Local Wrapper Function

A shell function that wraps claude and conditionally uses an API key is tempting but wrong. It adds latency (API calls over HTTP), creates another credential-bearing process on your machine, and doesn't isolate model versions. By routing to EC2, you get:

  • Model isolation: The EC2 box can run a different version of Claude than your local subscription.
  • Cost isolation: API usage on EC2 is separate and reportable; you can track farm-out spend independently.
  • Fault isolation: If the cheap Claude fails, it doesn't break your local environment.

Why EnvironmentFile, Not Shell Secrets

The EC2 daemon unit uses a systemd EnvironmentFile to inject credentials. This is better than storing them in the unit file itself because:

  • Credentials are not checked into version control (the .service file is generic).
  • Rotation is simple: update the env file, restart the service.
  • Auditability: you can log which file the daemon reads without exposing its contents.

Next Steps

  • Validate zshrc syntax: After removing the global export, source the modified ~/.zshrc in a new shell session and confirm the burst-valve function (referenced in notes) still loads correctly.
  • Test interactive flow: Run claude login and confirm it uses the keychain OAuth path (not API key prompts).
  • Test programmatic flow: Create a test script that sources repos.env and invokes the farm-out wrapper.
  • Monitor EC2 costs: Set up CloudWatch alarms on the Lightsail instance to track API invocation count and correlate with your monthly AWS bill.

Summary

The solution is a shell-level distinction: remove the API key from the interactive environment (forcing subscription auth), keep it available in repos.env for scripts, and route programmatic work to a dedicated EC2 instance running a local Claude service. This gives you fixed-cost interactive work, cost-optimized programmatic work, and clear auditability of where each request goes.