How I save tokens in Claude Code

Claude Code gets expensive when it has to read, rediscover and reason about things that do not matter, so I built my workflow around keeping its context small and intentional.

Claude Code can burn through tokens surprisingly fast.
You give it a tiny prompt like “fix the navbar” and it responds by inspecting half the repository, reading a small library worth of files, calling tools, running tests, retrying something, and eventually making a three‑line change. The prompt was small. The context was not.

That distinction completely changed how I use Claude Code.

I stopped thinking about token usage as a prompt‑writing problem and started treating context as a resource. The less irrelevant information Claude has to carry around, the more of its budget goes toward the thing I actually asked it to do. Here’s the setup I use.

Context over prompts

A Claude Code session contains much more than what you type. There are project instructions, files Claude reads, tool results, previous messages, skills, memory, MCP tools, and command output. All of that can end up in the context Claude has to reason over.

So this:

text
Fix the navbar.

can quietly turn into:

text
Read package.json
Read CLAUDE.md
Find the navbar
Read the layout
Read globals.css
Read several components
Search for navbar references
Edit the component
Run tests
Fix the test
Run tests again

The expensive part was never the sentence. It was everything Claude decided it needed to know before answering it.

My main rule is simple:

Give Claude enough context to solve the problem, but not enough context to distract it.

If you want a more formal walkthrough of how Claude Code thinks about context, the official docs at code.claude.com and guides like the “Claude Code 101” video on YouTube are worth a skim. I took the parts I liked and then ignored the rest, like a normal developer.

Keep CLAUDE.md boring

CLAUDE.md is one of the most useful files in a Claude Code project. It is also one of the easiest places to create a context dumpster fire.

I only put things there that Claude should know almost every time it works on the project. For example:

md
# Project
 
## Stack
 
- Next.js
- TypeScript
- Tailwind CSS
- PostgreSQL
- Drizzle
 
## Rules
 
- Use server components by default.
- Reuse existing components before creating new ones.
- Never use `any`.
- Do not modify `.env` files.
- Run `pnpm lint` after meaningful changes.
 
## Structure
 
- `app/` -  routes
- `components/` -  UI
- `lib/` -  shared utilities
- `db/` -  database

I do not put the entire architecture document in there.
I do not put every API endpoint in there.
I definitely do not paste a 500‑line design system in there “just in case”.

If Claude only needs something for one particular type of work, it belongs somewhere else.

My rule of thumb:

  • If Claude needs to know it every time, it goes in CLAUDE.md.
  • If Claude needs to know it sometimes, it becomes a skill.
  • If I’m tempted to paste a whole Confluence wiki, I go drink water and reconsider my life choices.

Skills instead of repeated lectures

Skills are reusable workflows. Anthropic explains them nicely in the skills docs, but the short version is: stop repeating yourself and let the skill do it.

Suppose I have a specific process for reviewing UI. Instead of typing this every time:

text
Use the existing components.
Keep spacing consistent.
Do not introduce unnecessary animations.
Check mobile layouts.
Check accessibility.
Follow the existing typography.
Do not redesign unrelated parts.

I turn it into a skill:

text
.claude/
└── skills/
    └── ui-review/
        └── SKILL.md

Then I can just say:

text
Use the UI review skill on this page.

The workflow becomes reusable instead of being copy‑pasted through conversation.

A reasonable skill library might look like:

text
.claude/
└── skills/
    ├── ui-review/
    ├── debugging/
    ├── code-review/
    ├── seo/
    ├── database/
    └── deploy/

I would rather have six small skills I actually use than one giant “my entire engineering philosophy” skill that scares both me and the model.

Here’s plenty for a UI review skill:

md
***
name: ui-review
description: Review a UI implementation for consistency, spacing, responsiveness and accessibility.
***
 
# UI review
 
1. Inspect the existing design system.
2. Check reusable components first.
3. Review spacing and typography.
4. Check responsive behavior.
5. Check accessibility.
6. Look for unnecessary visual complexity.
7. Return actionable findings.
 
Do not rewrite the application unless explicitly asked.

That’s it. The point is not to document the whole universe. The point is to give Claude a reliable workflow for one particular job.

Shameless skill plug

I also steal skills from the internet instead of reinventing everything.
One place I use is Qala, which has community skills for Claude Code and other agents. Yes, this is a slightly shameful injection of my own thing. Yes, I am still going to leave it here.

Make expensive skills manual

Some skills are useful but should only run when you really mean it. Deployment is the classic example.

I might define it like this:

yaml
***
name: deploy
description: Deploy the application to production.
disable-model-invocation: true
***

Now Claude will not spontaneously decide that deployment is relevant to “fix the navbar”.

I explicitly invoke it when I want it:

text
/deploy

This is useful for skills that:

  • perform side effects
  • contain large instructions
  • are rarely needed
  • should only run when explicitly requested

It keeps expensive workflows out of the normal path and prevents “I just wanted a button, why are we in production?” moments.

Search before you read

One of my biggest token‑saving habits is making Claude find the relevant files before reading them.

Prompts like:

text
Read everything in components and understand the application.

are how you accidentally pay for a complete codebase tour.

Instead I’ll ask:

text
Find where authentication is implemented.
Only inspect files relevant to the authentication flow.
Do not modify anything.

Claude can search first and then inspect the useful files. That difference matters in a large repository.

If a project has 300 components and I need one button, I don’t want Claude reading 300 components. I want it to find the button.

The same principle applies when I’m driving the tools manually. Search narrows the problem, reading expands it:

text
Find all references to `useAuth`.

then:

text
Read the files relevant to that flow.

The goal is not to stop Claude from exploring. It’s to make exploration deliberate instead of “open everything and hope for the best”.

Keep sessions clean: /context, /clear, /compact

When a session feels weirdly expensive, I check:

text
/context

It shows what’s actually occupying the context: a giant CLAUDE.md, a huge tool response, some massive file Claude read earlier, or just a conversation that has been going on for ten different tasks.

I treat /context as a diagnostic command. If the context is bloated, I fix that before throwing another prompt into the session.

I also stopped using one Claude session for everything. My old pattern looked like:

text
Build the landing page.
Fix the navbar.
Add authentication.
Debug the database.
Redesign the dashboard.

By the end, Claude had a huge history covering several unrelated problems and I had no idea what it still remembered.

Now I use:

text
/clear

when I move to a genuinely different task. A fresh session is often better than dragging three old problems into a new one.

/clear is for starting over.
/compact is for continuing the same task without carrying the entire conversation forward.

For example:

text
/compact Preserve the current implementation,
remaining bugs, test results and files being modified.

After a long debugging session, I don’t need Claude to remember every failed attempt. I need it to remember the current state. That distinction saves a lot of context and a lot of “wait, why are we talking about that old stack trace again?” moments.

Subagents for the big stuff

Subagents are one of my favorite ways to keep the main session clean.

Imagine asking:

text
Why is authentication broken?

Claude might investigate:

text
middleware.ts
auth.ts
layout.tsx
login.tsx
register.tsx
database schema
API routes
configuration

That investigation can become a huge amount of context.

Instead I delegate the research:

text
Investigate the authentication bug.
 
Do not modify anything.
 
Return:
1. Root cause
2. Relevant files
3. Recommended fix

The investigation happens in a separate context and the main conversation gets the useful conclusion instead of every step in the detective novel. This is exactly where subagents make sense.

Important detail: subagents are not free. If I spawn five agents to investigate a three‑file bug, I’ve probably made the problem more expensive and slightly ridiculous.

A subagent is useful when the work is large enough to justify its own context.

Good:

text
Investigate this large repository and find where the billing system is implemented.

Less good:

text
Spawn five agents and investigate this typo.

I use subagents to isolate expensive work, not to cosplay as a distributed systems course.

And I restrict what subagents can do. A research agent does not need permission to edit the repository:

yaml
***
name: researcher
description: Investigate the repository without modifying files.
tools: Read, Grep, Glob
model: sonnet
***

A researcher researches.
A reviewer reviews.
A deployer deploys.

The more specialized the agent, the less unnecessary work it tends to do.

MCP, tools, and hooks

MCP (Model Context Protocol) is extremely useful when I need external tools or data. It’s basically an open standard for connecting agents to tools.

But I don’t install an MCP server for everything.

If I need GitHub information once, I can just use:

bash
gh pr list

If I need a quick Git operation:

bash
git status

If I need to inspect a Docker container:

bash
docker ps

I already have a shell. I do not need an MCP server just because one exists and someone wrote a blog post about it.

MCP becomes more valuable when I repeatedly need a service or when the service does something awkward to access through the CLI.

The server itself isn’t always the problem; the output can be. Asking a logging service:

text
Give me all logs from today.

and getting thousands of lines back is a lot of context for Claude to process.

Instead:

text
Find authentication errors from the last hour.
Return only the relevant errors and timestamps.

Tools should return answers, not entire databases.

There’s a simple dividing line in my workflow:

  • If a task is deterministic, I prefer a tool or script.
  • If a task requires judgment, I prefer Claude.

Examples for tools:

text
format files
rename files
run tests
run lint
check git status
generate a known file

Examples for Claude:

text
decide how to refactor this
find the root cause
design the architecture
review this implementation

I don’t want to spend model tokens doing something a one‑line shell command can do perfectly.

Hooks live in the same world. If I always want formatting after editing a file, I don’t want Claude to remember:

text
Remember to run prettier.

I automate it.

Hooks are useful for deterministic actions that should happen at specific Claude Code lifecycle events. The principle is simple:

If something should happen every time, automate it instead of asking Claude to remember it every time.

It saves tokens and makes the workflow less fragile.

Models, rules, and memory

Not every task deserves maximum reasoning.

For “rename this variable”, I don’t need the biggest model on the menu.
For “why does this distributed caching system return stale data under concurrent writes?”, I probably do.

I roughly divide work into:

Simple

text
Fix this typo.
Rename this variable.
Explain this function.
Update this string.

Medium

text
Implement this API endpoint.
Refactor this component.
Fix this bug.
Add this feature.

Hard

text
Design this architecture.
Debug a complicated distributed issue.
Refactor an entire subsystem.
Investigate an unfamiliar codebase.

The expensive reasoning should go toward the problems that actually need it.

The same applies to rules. If I keep telling Claude:

text
Do not use `any`.

and then tomorrow:

text
Please stop using `any`.

that guideline has earned a permanent home:

md
## TypeScript
 
- Never use `any`.
- Prefer explicit types.
- Reuse existing types before creating new ones.

Anything I repeatedly correct usually belongs in CLAUDE.md, rules, or a skill:

text
Use existing components.
Don't touch environment files.
Run this test after API changes.
Don't add dependencies without asking.

On the knowledge side, one of the biggest sources of wasted context is rediscovery. Every time Claude has to figure out:

text
Where is the API?
How does authentication work?
What command runs tests?
Where are database migrations?
What conventions does this project use?
How is deployment handled?

it spends tokens learning something that could have been made explicit.

Good project configuration turns this:

text
discover
→ understand
→ ask
→ get corrected
→ retry

into this:

text
know
→ execute

I keep path‑specific rules close to the code that needs them:

text
.claude/
└── rules/
    ├── frontend.md
    ├── backend.md
    └── database.md

Frontend rules don’t need to load when I’m working on migrations. Database conventions don’t need to follow every UI request. It’s just another form of lazy loading:

text
Load what is relevant.

And I use memory for the things Claude keeps rediscovering:

text
The test command is pnpm test:unit.
The payment webhook lives in app/api/webhooks/stripe.
This repository requires Node 22.
The previous caching bug came from stale Redis keys.

I want memory to store recurring project facts, not become another giant documentation file.

My setup and checklist

If I were starting a project from scratch, I’d keep the structure simple:

text
project/
├── CLAUDE.md

└── .claude/
    ├── rules/
    │   ├── frontend.md
    │   └── backend.md

    ├── skills/
    │   ├── ui/
    │   │   └── SKILL.md
    │   ├── debugging/
    │   │   └── SKILL.md
    │   ├── review/
    │   │   └── SKILL.md
    │   └── deploy/
    │       └── SKILL.md

    └── agents/
        ├── researcher.md
        └── reviewer.md

The mental model looks like this:

text
CLAUDE.md

always-needed project knowledge
 
Rules

context-specific knowledge
 
Skills

repeatable workflows
 
Subagents

large isolated investigations
 
MCP

external tools and data
 
Hooks

deterministic automation
 
CLI

mechanical work

Each piece has a job. Nothing needs to do everything.

Before I start a large Claude Code session, I run through a quick checklist:

  • Is my CLAUDE.md actually necessary, or is it secretly a novel?
  • Am I asking Claude to read more files than it needs?
  • Can it search before reading?
  • Is this workflow better as a skill?
  • Is this investigation big enough for a subagent?
  • Do I really need this MCP server, or is git status enough?
  • Can a CLI command do this instead?
  • Can a hook automate this?
  • Should I use a cheaper model?
  • Is this session carrying unrelated history?
  • Should I run /compact?
  • Should I just start a fresh session with /clear?

Most of the time, at least one answer is “yes”, and fixing that saves a lot more tokens than arguing with the temperature parameter.

The mindset shift

The biggest improvement didn’t come from learning a secret Claude Code command. It came from changing the question.

I stopped asking:

How do I make Claude work harder?

And started asking:

How do I make Claude do less unnecessary work?

Give it less irrelevant context.
Give it better instructions.
Give it specialized skills.
Give expensive investigations their own context.
Use tools for deterministic work.
Use stronger reasoning only when the problem deserves it.
Keep sessions focused.

The goal is not to make Claude think less.
It is to make every token it spends actually matter.

Get in touch

anishhfn@gmail.com