One Weekend, $230/Month Saved

A build log of cutting AI infrastructure costs from $230/month to near-zero in one weekend: pre-computed caches, OAuth subscription routing, arguing with my own AI agent, and debugging Node.js version hell inside Kubernetes.

One Weekend, $230/Month Saved

This is the build log of a weekend where I took my AI infrastructure costs from ~$230/month in variable API spend to effectively zero. Two systems, two subscription arbitrage plays, a dozen debugging detours, and one argument with an AI agent who told me a feature was fake.

Everything here happened between Saturday morning and Sunday night. It's not polished. It's what actually happened.

Saturday Morning: The Pre-Compute Cache

Life Hub, my personal dashboard, makes about 10 distinct types of LLM calls: daily briefs, weekly summaries, council advisor reviews, news briefings, pillar narratives, correlation interpretations, and more. Every one of them hits the Anthropic API using Claude Haiku. A few hundred tokens per call, a few calls per day. The monthly bill was $2-5 in API costs.

That's not a lot. But I was also paying $20/month for a Claude Pro subscription that includes unlimited Opus (a significantly better model) through Claude Desktop. It sat there, unused, while Life Hub made paid API calls for the same kind of work.

Claude Desktop has a built-in scheduler. It can run prompts on a cron-like schedule. And it can connect to MCP servers, including Life Hub's, which exposes 157 tools. The plan: have Claude Desktop pre-compute all 10 LLM outputs every two hours using Opus, write them to Life Hub's database via MCP, and have Life Hub check for fresh pre-computed content before making its own Haiku calls.

The Implementation

New PreComputedCache table with a unique constraint on scope: only the latest result per scope matters. A get_precomputed() helper that checks freshness against a configurable threshold (default 6 hours). A save_precomputed MCP tool that Claude Desktop calls to write results back. Then a 3-line staleness check added to all 10 LLM generators:

precomputed = await get_precomputed("daily_brief")
if precomputed:
    return precomputed
# ... existing Haiku call as fallback ...

Seven files modified, same pattern each time. The pre-computed check runs first because it's both higher quality (Opus vs Haiku) and zero marginal cost.

The Node.js Version Wall

Connecting Claude Desktop to Life Hub's MCP server should have been simple: add a life-hub entry to claude_desktop_config.json using mcp-remote as the bridge. It wasn't.

My Mac has Node 25 via Homebrew at /opt/homebrew/bin/node. It also has an ancient Node 18.16.0 at /usr/local/bin/node from a forgotten install. Claude Desktop's default PATH puts /usr/local/bin first. Even after pointing the command at Homebrew's npx, when npx spawns the mcp-remote child process, it inherits the default PATH and finds Node 18 first.

ReferenceError: File is not defined
    at .../undici/lib/web/webidl/index.js:537:48
Node.js v18.16.0

The undici HTTP library in mcp-remote uses the File global, which only exists in Node 20+. Fix: add "env": {"PATH": "/opt/homebrew/bin:..."} to the MCP server config so the child process finds Node 25 first. Also had to nuke the stale npx cache at ~/.npm/_npx/ that had modules compiled against Node 18.

First Successful Run

Once connected, the scheduled task ran and produced exactly what I'd hoped for: Opus-quality daily, weekly, and monthly briefs. Four council advisor comments. A weekly retrospective. The quality difference between Opus and Haiku was immediately noticeable: the Opus advisor caught a data integrity issue (connection pillar scoring high but connection goals all at 0% logged) that Haiku likely wouldn't have surfaced.

Saturday Afternoon: Timestamps and Auditing

With pre-computed content flowing, a new problem appeared: none of the AI-generated content on the dashboard showed when it was generated or what model made it. Was this brief from 2 hours ago (Opus) or 12 hours ago (Haiku fallback)? No way to tell.

I built a load_summary_with_meta() function that returns the timestamp and model alongside the content, a _format_relative_time() helper ("2h ago", "yesterday"), and an _ai_timestamp_html() renderer. Every AI card now shows "Generated 2h ago · Haiku" or "Generated just now · Opus".

Then I discovered the monthly digest page (/digest/monthly) was missing its AI Review card entirely. The weekly and daily pages had theirs. The monthly page just never got one. Added the full card with lazy-loading, refresh button, and timestamp display.

Three rounds of Chrome-based auditing followed. Browse every page, check every AI section, fix what's missing, redeploy, browse again. The GHCR build pipeline kept failing on ruff lint errors (import sorting). Two deploys ran the old image because I didn't wait for the build to pass before restarting.

An interesting discovery during the audit: most of the "AI content" on pages like /today and /morning isn't actually LLM-generated. Mood forecasts, energy predictions, predictive nudges, next actions: these are all rule-based algorithms (Pearson correlations, scoring heuristics, linear regression). No LLM involved. Those got "AI" badges instead of "Generated X ago" timestamps, because they're always fresh: computed on every page load.

Saturday Evening: The OpenClaw Argument

Life Hub's API costs were modest ($2-5/month). The real hemorrhage was OpenClaw, my multi-agent AI system with 7 agents routing everything through OpenRouter's API. The bill: $5-10 per day. That's $150-300 per month.

I knew ChatGPT Plus ($20/month) includes the same class of models I was paying per-token for. So I asked Henry, OpenClaw's orchestrator agent, to set up OAuth subscription auth.

He pushed back. Hard.

"OAuth via ChatGPT Plus is not a feature OpenAI offers. Full stop. OpenAI's API and ChatGPT Plus are separate products with separate billing."

He was confident. He cited the product separation, flagged my Reddit sources as unreliable, and dismissed the concept three times. When I sent him a Reddit post about it, he correctly identified some fake methods (Gemini OAuth, "GPT-5") but threw out the legitimate one (OpenAI Codex OAuth) along with them.

I pointed him to the official OpenClaw documentation. The docs explicitly state: "OpenAI Codex OAuth is explicitly supported for use outside the Codex CLI, including OpenClaw workflows." Full PKCE flow documented.

To Henry's credit, he corrected immediately: "You're right. I owe you a correction." No hedging. Clean reversal. But the lesson stands: AI agents can be overconfident about what's "fake," especially for features that postdate their training data.

OAuth in a Headless Kubernetes Pod

OpenClaw runs on Talos Linux: an immutable, API-only OS. No SSH, no desktop, no browser. The OAuth flow needs an interactive TTY, a browser, and a callback listener on 127.0.0.1:1455. None of these exist inside a container.

# Terminal 1 — Route OAuth callback into the pod
kubectl port-forward -n openclaw deploy/openclaw 1455:1455

# Terminal 2 — Interactive onboard wizard
kubectl exec -it -n openclaw deploy/openclaw -c openclaw -- \
  sh -c 'openclaw onboard --auth-choice openai-codex --accept-risk'

Copy the OAuth URL from the wizard, open it on the Mac, authenticate, and the browser redirects to localhost:1455. The port-forward catches it and routes it into the pod.

Debugging highlights along the way:

  • Shell splitting: zsh kept interpreting multi-line kubectl exec commands as separate commands. Fix: sh -c '...' with single quotes, all on one line.
  • Wrong entry point: openclaw models auth login --provider openai needs a provider plugin that wasn't installed. The Codex OAuth flow is built into onboard, not the plugin system.
  • OpenAI outage: first attempt hit "Service Unavailable" on the consent page. Waited, retried, worked.
  • Ephemeral config: Henry updated the model config inside the pod. Pod restarted. Config gone. Fix: update the Kubernetes ConfigMap in the GitOps repo so it survives restarts.

The Switch

Updated the ConfigMap to route all 7 agents through openai-codex/gpt-5.3-codex as primary, with OpenRouter as fallback:

"model": {
  "primary": "openai-codex/gpt-5.3-codex",
  "fallbacks": [
    "openrouter/anthropic/claude-sonnet-4-6",
    "openrouter/anthropic/claude-haiku-4-5"
  ]
}

Committed, pushed, Flux reconciled, pod restarted with the new config. Every agent now hits the ChatGPT subscription first and only falls back to paid API on rate limits or outages.

The Numbers

SystemBeforeAfterHow
OpenClaw (7 agents)$150-300/mo$0ChatGPT Plus OAuth
Life Hub (pre-computable)$2-5/mo~$0Claude Desktop pre-compute
Life Hub (event-driven)$1-2/mo$1-2/moStill Haiku API (Phase 3)
Total variable$153-307/mo~$1-2/mo
Subscriptions (fixed)$40/mo$40/moAlready paying

The remaining $1-2/month is event-driven LLM calls in Life Hub: trigger commentary, thought classification, journal sentiment analysis. These fire in response to user actions and can't be pre-computed. Phase 3 of the roadmap routes these through the Codex OAuth token too.

The Pattern

Both optimizations exploit the same gap in AI pricing: subscriptions include models at fixed cost that are expensive per-token via API.

  • Claude Pro ($20/mo): Claude Desktop pre-computes Life Hub content with Opus every 2 hours
  • ChatGPT Plus ($20/mo): OpenClaw routes all agent work through Codex OAuth
  • API keys: fallback only, fires ~5% of the time

The fallback is what makes it production-viable. Subscriptions have rate limits, outages, token expiry. The per-token API is always there. You get the cost savings 95% of the time and graceful degradation the other 5%.

What I Learned

AI agents can be overconfident about what's "fake." Henry was sure Codex OAuth didn't exist. He was wrong. The feature postdated his training data and contradicted his mental model of how OpenAI products work. Good agents have strong opinions weakly held, but you have to be the one who forces the re-evaluation by pointing at primary sources.

Node.js version hell follows you everywhere. Not just your app runtime, not just your CI: it follows you into MCP bridge tools, into npx caches, into child processes spawned by child processes. If you have multiple Node versions installed, expect pain.

OAuth in headless K8s pods is solvable. kubectl port-forward + kubectl exec -it bridges the gap between a headless container and a Mac with a browser. It's not elegant, but it works. Keep the commands saved for when the refresh token expires in 30-90 days.

The real cost of AI isn't the model. It's the billing model. The same capability costs $0/month through a subscription and $150-300/month through an API. The models are the same. The quality is the same. The billing structure is the only variable.

Every "AI-generated" label you add makes your dashboard more trustworthy. Adding timestamps and badges to AI content felt like a small UX task. But knowing whether a brief was generated 2 hours ago by Opus or 12 hours ago by Haiku changes how you read it. Transparency about provenance isn't overhead. It's trust.

Total time: one weekend. Total savings: ~$230/month. Total infrastructure changes: 0. It's the same cluster, the same pods, the same tools. Just smarter routing.