A weekend's invoice, and why it wasn't Claude's fault
My first Claude Code session that genuinely impressed me ended with a bill of several hundred dollars for a single weekend of work. Mid-size project refactor, dozens of "just one more thing" iterations, several "let's start over" resets when the context drifted. Back then I thought: this model is just expensive.
A few months later a comparable scope of work costs me several times less. I didn't downgrade. I learned how Claude counts tokens, and how not to expose it to full price unnecessarily.
This article collects what I learned about token optimization in one place. Concrete patterns, concrete pitfalls. Treat the dollar figures as illustrative. They depend on your project and the current price list.
Tokens — quick math
Anthropic API pricing for the Claude family (order-of-magnitude figures, current in 2026, USD per million tokens; check Anthropic's current price list before you budget, since models and rates change):
- Claude Haiku 4.5: $1 input / $5 output
- Claude Sonnet 4.6: $3 input / $15 output
- Claude Opus 4.7: $5 input / $25 output
Looks innocent. The catch: output is 5× more expensive than input, and "write me the whole component" sessions generate a lot of output. Plus: each "no, do it differently" iteration sends the entire prior context as input. For a ~50K-token prompt at Opus rates that's roughly $0.25 per iteration, so a few dozen iterations add up to several dollars just for Claude to "listen".
Three levers cut costs dramatically:
- Prompt caching: a prefix read from cache costs 0.1× input price. The same ~50K prompt is then roughly $0.025 instead of $0.25 (about 10× cheaper).
- Model selection: Haiku for classification, Sonnet for most work, Opus only for the hardest decisions.
- Context shrinking: plan mode + /compact + subagents = fewer tokens at every stage.
The full Polish version covers each lever in depth. Below: the highlights.
Prompt caching is a prefix match
The Anthropic API treats cache as prefix match. Any byte change anywhere in the prefix invalidates everything after it. Render order is tools → system → messages. A breakpoint on the last system block caches tools + system together.
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 16000,
system: [
{ type: "text", text: largeSystemPrompt,
cache_control: { type: "ephemeral" } } // 5-min default TTL
],
messages: [{ role: "user", content: userQuestion }]
});
Break-even after two requests with the same prefix. Verify via response.usage.cache_read_input_tokens. If it's zero across repeated identical-prefix requests, a silent invalidator is at work (timestamp in system prompt, non-deterministic JSON keys, varying tools list).
Model selection — when to use what
Haiku 4.5: classification, parsing, subagent file reading, latency-sensitive workloads. Weak at multi-step reasoning and code review.
Sonnet 4.6: sweet spot for most Claude Code work. Important note: default effort on 4.6 is high, so if you migrated from 4.5 without tuning, your bills can climb noticeably. Set output_config: {effort: "medium"} explicitly for cost-aware workloads.
Opus 4.7: long-horizon agentic, architecture decisions, vision-heavy. Note breaking changes vs 4.6: budget_tokens removed (use thinking: {type: "adaptive"}), temperature/top_p/top_k removed, new "xhigh" effort level (sweet spot for coding/agentic, used as default in Claude Code), high-res vision (2576px long edge, 1:1 pixel coordinates).
The subagent pattern
The most powerful Claude Code optimization: main agent on Opus, subagents on Haiku. The Explore agent reading code (find/grep/read) doesn't need Opus reasoning. Haiku at $1/$5 per 1M instead of Opus at $5/$25 is a roughly 5× cost reduction for the same task. In my own linkedin-mcp-server project (an illustrative example, not an audited benchmark), moving comment classification to a cheaper model and reserving Opus for the few comments worth a full reply cut that process's cost by roughly an order of magnitude, with no drop in reply quality.
Adaptive thinking + effort
The old thinking: {type: "enabled", budget_tokens: 8000} is deprecated on Sonnet 4.6 / Opus 4.6 and removed on Opus 4.7 (400 error). Replaced by thinking: {type: "adaptive"} + output_config: {effort: ...}.
Adaptive = Claude self-decides when and how much to think. Effort controls depth + token spend. Five levels on Opus 4.7: low / medium / high / xhigh (4.7+, sweet spot for coding) / max (Opus tier only). Sonnet 4.6 supports low/medium/high (and max). Haiku doesn't support effort.
For a hard cumulative cap on agentic loops, use task_budget (beta header task-budgets-2026-03-13, Opus 4.7 and newer): the model sees a running countdown and self-moderates.
Plan mode + Auto mode + /compact
Plan mode splits work across models: Explore agent on Haiku → Plan agent on Sonnet → main agent on Opus implements with the plan in hand. This can bring a feature down to a fraction of the cost of doing it all on a single agent.
Auto mode minimizes user round-trips. Each "can I do X?" confirmation costs ~500 tokens of preamble. Use auto mode for routine tasks in known context.
/compact manually summarizes session history before the API does it automatically (server-side compaction triggers at ~150K tokens with beta header compact-2026-01-12). Trade-off: compaction resets the cache. Net positive if you have a long queue of work in the same session.
Skills + MCP — load on demand
Every MCP tool registered in a session = ~hundreds of tokens in base prompt, multiplied by every request. Worst antipattern I've seen: tools = buildToolsForUser(user): every user has a different tools array, no one shares cache, total miss across the whole user base.
Solutions:
- Stable baseline tools, dynamic discovery via
tool_search(appends schemas, preserves cache). - Skills instead of tools for domain knowledge: small description in prompt, body lazy-loaded on demand.
- Parameterized custom tools: one
db_querywithoperationparam instead ofdb_select/db_insert/db_update.
Measurement
Without monitoring all the above is wishful thinking. I track:
- Per-request usage logged to SQLite with session_id, model, endpoint. Daily aggregations: tokens by model, cache hit rate, top 10 most expensive sessions.
- Per-feature labeling. This can surprise you: a "small" process that runs every few minutes on full context can eat a disproportionate slice of the budget. Moving it to Haiku and keeping a high cache hit rate can cut that cost by an order of magnitude.
- Token counting before sending via
client.messages.countTokens(), useful to verify a prompt clears the cache prefix minimum (Opus 4.7 = 4096 tokens; smaller prompts silently don't cache).
What's next
This is the baseline. Each area (caching architecture, model selection trees, plan mode patterns, MCP topology) has deeper optimization layers covered weekly in the bartoszgaca.pl newsletter, driven by GA4 + GSC data from bartoszgaca.pl, so each edition addresses real queries people search for. First edition this Wednesday.
Subscribe: Claude Code, token optimization, MCP, AI dev workflow. No clickbait, weekly.
If you want these patterns applied to your own Claude Code or agent setup, check AI automation for business or Book a free call (30 min) and I will walk through your token bill with you.
For the full deep dive (Polish, with a worked before/after example and a silent-invalidator audit checklist): Polish version →
— Bartek
kontakt@bartoszgaca.pl