Context is a limited resource, and understanding what fills it matters more than any saving tricks. It is consumed not only by your messages: the system prompt and built-in tools, memory and CLAUDE.md, MCP tool schemas if tool search is off, read files and command outputs, the content of loaded skills, images, summaries and subagent results, long error logs. The /context command shows this filling - and it is where to start when work has slowed or grown expensive.
When the context fills, compaction kicks in - compressing the history into a summary. Auto-compact is usually on, /autocompact sets the window, and /compact makes a summary right now. Before compaction it helps to say explicitly what to keep: the root cause, the accepted API contract, the immutable boundaries, the failing test and the current verification status. Otherwise you risk losing in compaction exactly what the work was for - and it cannot be recovered from the summary.
Compaction has a cost that is rarely thought about. It creates a new conversation prefix and therefore requires rebuilding the cache; if the old cache has already expired, the summarization itself may re-read a large cold history. Hence the practice of calling /compact at a natural task boundary rather than in the middle of a debug trace: compaction at a bad moment is both more expensive and riskier for the context you still need.
It helps to understand what resets the cache and what preserves the prefix - this directly affects cost. The cache is reset by a model or effort switch if the request shape changes, the first enabling of fast mode, connecting or disconnecting upfront MCP tools, reloading a plugin, a bare deny of a whole built-in tool, /compact itself and a Claude Code upgrade that changed the system prompt. The prefix, on the other hand, usually survives file edits, a permission-mode switch, a skill call, /recap and a rewind to an existing prefix.
Worth remembering separately is a counterintuitive detail: changing CLAUDE.md in the middle of a session does not reset the cache, because it is not applied at all until clear, compact or restart. Many edit the memory and expect the agent to follow it at once - but a new CLAUDE.md is picked up only on the next prefix. This is not a bug but a consequence of caching, and knowing it keeps you from wondering why "I fixed the instruction, but the behavior is the same".
Tool search sharply reduces the startup context for MCP. By default compatible models defer MCP tools and pull the needed schema via search - instead of loading all schemas upfront. This is a big saving, but it is tied to the protocol: a custom ANTHROPIC_BASE_URL or a proxy that does not pass tool_reference blocks can break it, and then ENABLE_TOOL_SEARCH is enabled only if the proxy definitely supports the protocol.
Tracking spend keeps you from flying into cost blindly. /usage on a subscription shows the plan limits and activity, but the session cost there is not a bill. Under API billing you look at the provider's or Console's usage and treat the local calculation as an estimate. For scripted runs a bare mode with output-format json is handy: from the response you can pull the cost, usage and model with a single jq and aggregate across runs rather than guess.
Spend is reduced without worsening the result by quite concrete techniques: keep CLAUDE.md short and move procedures into skills; use path-scoped rules; do not paste large logs but save the file and give the path; limit exploration to a specific entry point; hand noisy reading to a subagent and get back a summary; enable MCP tool search; start a new session for a new task; use a cheaper model only where quality is measured. A narrow context is both cheaper and smarter than one huge prompt.
# Say what to keep BEFORE compaction
/compact Keep the root cause, the accepted API contract,
the immutable boundaries, the failing test and the current verification status.
# A mid-session CLAUDE.md edit is not applied until clear/compact/restart# Estimate the cost of a scripted run
claude --bare -p "summarize README.md" --allowedTools Read --output-format json \
| jq '{cost:.total_cost_usd, usage:.usage, model:.model}'
# /usage on a subscription - plan limits, not a bill