← All posts

Using Claude Code without burning tokens

Gjorge Karakabakov

The first month I used Claude Code seriously, I left Opus as the default and never thought about it again. It worked. It also cost about three times what it needed to, and the reason wasn’t that Opus is expensive — it’s that I was paying Opus rates to rename variables.

Model choice is the dial everyone reaches for first, and it is genuinely the biggest one. But it’s one of four, and the other three are where most of the waste actually lives. Here’s the whole set.

The tiers, and what each one is for

List prices per million tokens, input / output:

ModelInputOutputContext
Fable 5$10$501M
Opus 5$5$251M
Sonnet 5$3$151M
Haiku 4.5$1$5200K

Fable 5 sits above Opus and is worth knowing about — /model fable, or the best alias — but it isn’t the default on any account type and it isn’t what this post is about. The working set is the bottom three.

Opus is five times Haiku and roughly 1.7× Sonnet at list price. Sonnet 5 is on introductory pricing of $2 / $10 until the end of August 2026, though, which makes the gap 2.5× right now — worth knowing before you build a habit around a ratio that’s about to move.

Those ratios are worth internalising either way, because they’re smaller than the intuition most people carry around. Opus is not ten times the price of Sonnet, and treating it as a luxury you ration by the sip gets you the worst of both: you use it too little for the thinking, and the cheaper model then takes twelve turns to converge on something the expensive one would have got in two.

The split that has worked for me:

Opus plans and diagnoses. Anything where being wrong is expensive downstream: scoping a feature, working out why an intermittent test fails, deciding how a migration should be sequenced, reading unfamiliar code and telling you what it actually does. The output here is small — a plan, an explanation — but its quality determines how much work happens next. This is the one place where paying more is unambiguously cheaper.

Sonnet executes the plan. Once the approach is settled and the files are named, the work is mostly mechanical judgment: write the code, follow the existing patterns, run the tests, fix what broke. Sonnet 5 is very good at this and is what I leave as the session default.

Haiku does the errands. Reformatting, renaming across a directory, extracting a list from a log, generating fixtures, first-pass triage of a big file. Anything where you’d have written a script if you’d felt like it. Haiku’s 200K context is a real constraint, so this is per-task work, not “hold the whole codebase in your head” work.

/model switches the current session and saves the choice as your default for new ones — it stopped being a session-only toggle a few releases back, which surprises people who reach for it to try something once. /config and the model setting cover the same ground. For subagents, set model: haiku in the subagent’s own configuration and it stays cheap regardless of what the main session is running.

There’s also opusplan, which is this whole section as a built-in: Opus while you’re in plan mode, Sonnet the moment you leave it. It’s the right default for a lot of people. Read the next section before you turn it on, though, because it has a cost that isn’t in the name.

The handoff has a cost most people miss

Here’s the part that isn’t obvious. The model is part of the cache key. Each model has its own cache, so the moment you /model from Opus to Sonnet halfway through a long session, the next request reads the entire history with no cache hits and writes it all again under the new model. The reads you’d been getting at roughly a tenth of the input rate are gone for that turn.

Worth being precise about what that costs, because it’s easy to overstate. The re-read isn’t billed at full input price — it’s a cache write, which is 1.25× the base input rate on the five-minute cache and 2× on the one-hour one. But it’s charged at the rate of the model you switched to. Opus → Sonnet is a downgrade, so you’re re-processing at Sonnet’s cheaper rates:

200K-token session
One more Opus turn, cache warm200K × 0.1 × $5 = $0.10
Switching to Sonnet, one-time200K × 1.25 × $3 = $0.75
Each Sonnet turn afterwards200K × 0.1 × $3 = $0.06

So the switch costs about seven or eight turns’ worth of what you were already paying, then starts paying you back. Annoying, not ruinous — and if you’re going to keep working for a while, it clears.

The direction that actually hurts is the other one. Sonnet → Opus on the same context is 200K × 1.25 × $5 = $1.25, against the $0.06 a turn you were paying on Sonnet. That’s twenty turns of savings gone on one keystroke, and it’s the switch people make casually, because it feels like reaching for the better tool rather than restarting a meter.

Two things that soften it. The old cache isn’t destroyed, only abandoned — it lives out its TTL, so bouncing Opus → Sonnet → Opus inside the window can still hit the original prefix. And the cost is once per switch, not once per turn.

So make the handoff a boundary, not a swap:

  • Plan with Opus, then /clear and start the implementation session on Sonnet with the plan pasted in. New session, new cache, and the fresh context is smaller anyway.
  • Or keep the main loop on one model and push the cheap work into subagents. A subagent starts its own conversation with its own system prompt and builds its own cache, so delegating to Haiku leaves the parent’s prefix untouched — the call and its result just append to your conversation. This is the better pattern for anything you do repeatedly. (Subagents run on the five-minute TTL even on a subscription, so a long-lived one has its own cold starts. It just isn’t your problem.)

Which brings back opusplan. Every toggle in and out of plan mode is a model switch, with exactly the cost above, and plan mode is something you flip more than once in a session. It’s still a good setting — it just isn’t free, and it’s the one case where the tooling makes the expensive move so convenient you stop counting it.

The general rule: switching models is cheap between conversations and expensive inside one.

Effort is the dial nobody touches

Most current models take an effort level — low, medium, high, xhigh, max — which controls how much they think before acting. Thinking tokens bill as output tokens, at output rates, and the default budget can run to tens of thousands of tokens per request. On a reasoning-heavy task that’s money well spent. On “rename this function everywhere” it is pure burn.

The exception matters here, though: Haiku 4.5 has no effort level at all. Opus 5, Sonnet 5, Fable 5 and the 4.7/4.8 Opuses take the full lowmax range; Opus 4.6 and Sonnet 4.6 take everything but xhigh. Haiku isn’t on the list, so “run the errands on Haiku at low effort” is one dial, not two. Set a level the active model doesn’t support and Claude Code quietly clamps to the highest one it does.

high isn’t just a sensible default — it is the default on every model that supports effort, Opus 4.7 aside, which defaults to xhigh. So the useful moves are downward. xhigh earns its keep on genuinely hard agentic coding; low is right for anything mechanical; max is session-only and won’t persist.

The catch, which is the same catch

Here is the thing I had wrong when I first wrote this section, and it undercuts the obvious advice: effort is part of the cache key too. Same model, different effort level, different cache. Running /effort mid-conversation invalidates the prefix exactly the way /model does — Claude Code will now show you a confirmation dialog before it applies one, which is a decent tell that it isn’t free.

So “drop to low for this batch of routine edits, then put it back” is, on a large context, two full re-reads to save output tokens on a handful of cheap turns. That can easily lose. The saving is real, but it belongs at the top of a session, not in the middle of one — pick the level when you pick the model, and let it ride.

When you want one deep think without paying to move the dial, put ultrathink anywhere in the prompt. Claude Code recognises the keyword and adds an in-context instruction; the effort level sent to the API doesn’t change, so the cache survives. It’s the cache-safe version of the thing everyone actually wants. (“think hard”, “think more” and friends do nothing — they’re passed through as ordinary text.)

With that caveat attached, this is still the dial with the best ratio of impact to effort, and almost nobody moves it. Starting a session of routine edits at low rather than high is often a larger saving than dropping from Opus to Sonnet, because you’re cutting the expensive half of the bill — output — rather than the cheap half.

Which is really the whole argument in one line: model, effort and context, chosen together and chosen early. Not one of the three, picked once, and left alone — and not all three, fiddled with continuously, which is its own way of paying twice.

Your context window is the actual bill

Claude Code sends the full conversation with every request. Every tool call sends it again, with the tool results attached. Prompt caching means you re-read that history at a discount rather than full price, but a one-line question in a session that’s been open all day still draws against the entire conversation.

Three habits, in descending order of impact:

/clear between unrelated tasks. Stale context is a tax on every subsequent message, and it never gets cheaper. /clear itself costs nothing. /compact does cost something, but I had the reason wrong: yes, it sends a separate request that reads the whole conversation in order to summarise it — but while the cache is warm, that request reads your prefix from cache like any other, so a mid-session compact costs a fraction of what the context size suggests. Most of it is generating the summary.

The expensive compact is the cold one. Resume a session the next morning, run /compact, and there’s no cache left to read, so the summarisation request reprocesses the entire history as uncached input. Same command, wildly different bill. Compact at a natural break while you’re still working; don’t compact as the first thing you do after a gap.

And if you’re compacting because you went down a path you want to abandon, use /rewind instead. It truncates back to a prefix the cache was already built from, so the next request hits the earlier entry rather than building a new one. Compaction constructs a history that has never been cached; rewinding returns to one that has.

Watch the cache lifetime. Come back from lunch and your first message misses the cache entirely and reprocesses everything. An hour on a subscription, five minutes on an API key or a cloud provider — and, the part that catches people, it silently drops to five minutes on a subscription too once you’re over your plan limit and drawing on usage credits, because that’s the point where cache writes start costing you money. On Pro and Max, resuming a large session after a long break will offer to resume from a summary instead, which is usually the right answer.

Restarting is a cache miss too. Upgrading Claude Code rewrites the system prompt, so the first turn afterwards rebuilds from the top — and resuming a long conversation after an upgrade re-reads the entire history behind a prefix that no longer matches. That first turn back into a big session can genuinely be the most expensive request you send all week. Same for connecting or disconnecting an MCP server whose tools aren’t deferred, toggling a plugin that ships one, or adding a bare tool name as a deny rule. The cache is also scoped per machine and per directory, so two worktrees of the same repo never share one.

Move instructions out of CLAUDE.md. CLAUDE.md loads into context at session start, every session, whether or not it’s relevant. If it has grown a detailed PR-review procedure and a database-migration runbook, you’re paying for both while writing CSS. Keep it under a couple of hundred lines of genuine essentials and move the procedures into skills, which load only when used.

/context shows what’s currently taking up space. /usage shows what it cost, broken down by model — and on a Pro, Max, Team or Enterprise plan it also attributes recent usage to specific skills, subagents, plugins and individual MCP servers, so you can see which of your own tools is expensive. It flags behaviours responsible for 10% or more of recent usage, which is how you find out that half your spend is cache misses. Two caveats: that breakdown is the subscription view, not the API one, and the dollar figure is computed locally at list rates, so it ignores promotional pricing and contracted discounts. Treat it as a distribution, not an invoice.

Skills: the on-demand half of your context

A skill is a folder with a SKILL.md in it. The frontmatter description sits in context permanently — it’s how Claude decides whether the skill is relevant — and the body loads only when the skill actually runs. That asymmetry is the whole point: a 2,000-word procedure costs you one sentence of context until the moment you need it.

Writing one

Personal skills live in ~/.claude/skills/<name>/SKILL.md and apply everywhere; project skills live in .claude/skills/<name>/SKILL.md and are committed with the repo, so everyone on the team gets them.

mkdir -p .claude/skills/review-migration

Then SKILL.md:

---
name: review-migration
description: Reviews a database migration for lock risk,
  reversibility and data loss before it ships. Use when the user
  adds or edits a file in db/migrations.
paths: db/migrations/**
model: opus
effort: high
---

Review the migration against these, in order:

1. **Locking** — does any statement take an ACCESS EXCLUSIVE
   lock on a table with significant rows? Adding a column with
   a default, changing a type, and adding a non-concurrent
   index all do.
2. **Reversibility** — is there a `down`, and does it actually
   restore the prior state rather than just dropping what `up`
   created?
3. **Data loss** — any `DROP`, any narrowing type change, any
   `NOT NULL` added to a column with existing rows.
4. **Ordering** — does this migration assume application code
   that hasn't deployed yet? Schema and code deploy separately.

Report each finding with the statement, the risk, and the safe
rewrite. If all four pass, say so in one line — don't pad it.

Two things there are worth pulling out.

The description is doing real work. It’s the only part Claude sees before deciding to load the skill, so it needs to say when to use this, not just what it is. “Reviews migrations” is a poor description; the version above names the trigger. It gets truncated at 1,536 characters in the listing, so front-load the use case.

And model: opus / effort: high are per-skill overrides. That’s the cost story coming full circle: this particular review is worth Opus at high effort, and it says so in its own frontmatter, so the session can sit on Sonnet at medium and only escalate for the ten seconds it matters. The override lasts for the rest of the current turn and isn’t written to settings — your next prompt is back on the session model. You can go the other way too: model: haiku, effort: low on a formatting skill.

Given everything above, be honest about what that escalation is: a model and effort change, which is a cache-key change, there and back. For a skill you fire two or three times a session on work that genuinely needs the better model, that’s a fine trade. For one that runs on every edit, you’ve automated the expensive keystroke — which is worse than doing it by hand, because now nothing prompts you to notice.

context: fork sidesteps that: the skill runs as a subagent with its own context, so the model it uses is its own business and the intermediate work never lands in your conversation. The result does come back — that’s the point — but the twenty tool calls that produced it don’t. It runs in the background by default and won’t see your conversation history, so it only makes sense for skills that contain an actual task rather than a set of guidelines. Set background: false if you’d rather wait for the result in the turn that invoked it.

Invoke it with /review-migration, or let Claude pick it up on its own when you touch a matching file. paths scopes automatic loading so it doesn’t fire while you’re working on something unrelated.

The part people skip: skills expire

Here’s a point Boris Cherny, who created Claude Code, has made — and it’s the one I’d most want people to take away.

Revisit your skills with every model release. A skill is, in large part, a correction: you wrote it because the model kept doing something you didn’t want, or kept missing a step. Newer models often don’t need the correction. Worse, they can perform better without it, because your carefully enumerated procedure overrides judgment that has since become better than the procedure.

I’ve seen this concretely. A “check the tests actually ran, don’t just report success” instruction that was load-bearing on one model became redundant on the next and then became actively counterproductive on the one after, where it produced a verification step on every trivial edit. The skill was right when I wrote it. It just stopped being right, silently, and nothing told me.

So build it into your release routine. When a new model lands:

  1. Take one skill and delete it — or comment out the half you suspect is obsolete.
  2. Run a real task that would have used it.
  3. Compare. If the output is as good or better, the skill was overhead. If it’s worse, you’ve re-earned it.

This costs half an hour per release and it is the highest-leverage half hour in the whole workflow, because skill bloat compounds in exactly the way context bloat does: invisibly, on every single request.

Where to learn the craft of it

If you want to go deeper on skills as a discipline rather than a config format, Matt Pocock’s skills repository is the best resource I’ve found. It’s a curated set — “Skills for Real Engineers” — aimed squarely at the failure modes people actually hit: agents that drift from intent, output that sprawls, architecture that degrades a little with every session.

Some of what’s in there:

  • /grill-with-docs — interview-style planning that pins down the domain model before any code gets written
  • /tdd — a genuine test-first workflow rather than tests bolted on afterwards
  • /diagnosing-bugs — structured debugging instead of guess-and-check
  • /improve-codebase-architecture — scans for structural problems and proposes fixes
  • /handoff — carrying knowledge between sessions, which is directly a cost technique: a good handoff is what lets you /clear without losing your place

His YouTube channel covers the same material with the reasoning shown, which is more useful than the finished artefacts — you can copy someone’s skill, but you can’t copy their judgment about when to write one.

The short version

  • Opus to plan and diagnose, Sonnet to build, Haiku for errands.
  • Switch models between sessions, not inside one — the model is part of the cache key. Upward switches cost the most; opusplan makes them frequent.
  • So is the effort level. Choose both at the top of a session and leave them; use ultrathink when you want one deep turn without moving the dial.
  • Haiku has no effort level, so there’s only one lever there.
  • Push cheap work into subagents; they build their own cache and leave yours intact.
  • /clear between tasks — it’s free. /compact is cheap while the cache is warm and expensive once it’s cold, so compact at a break, not after one. /rewind beats /compact for abandoning a path.
  • Keep CLAUDE.md thin, put procedures in skills.
  • Delete a skill every release and see if you miss it.

None of this is about using Claude Code less. It’s about not paying Opus to do Haiku’s job — and, just as often, not paying Sonnet to redo work Opus would have got right the first time. Run /usage at the end of a working day for a week. The distribution will tell you which of those two you’re actually doing.