Chapter 4. Claude Code Sessions: Context, Compaction, and What a Run Costs
Claude Code loads the system prompt, the CLAUDE.md stack, the
auto-memory index, the per-turn skill listing, and a git snapshot into
your context window before you type a word. When the window fills, it
clears older tool outputs first, then summarizes the conversation.
/context shows the grid and /usage shows what
the session cost.
A session spends two budgets. The first is the context window, which pays for instructions, file contents, tool output, and the conversation itself. The second is money, and it shadows the first: a full window rebuilt on a cold cache is a real charge, not a rounding error. I run enough concurrent sessions across the estate that I read both meters on a schedule, not on suspicion.
What fills the window before your first message
Before the first message lands, the session has loaded the system prompt, the CLAUDE.md stack, the auto-memory index, the per-turn skill listing, and a git snapshot. An empty prompt is not an empty window.
Each of those has a mechanism or a number attached. CLAUDE.md content
arrives as a user message after the system prompt, not inside it; an
instruction that needs system-prompt authority goes through
--append-system-prompt instead. The stack can get fat
because discovery walks up the directory tree from the working directory
and concatenates each file it finds, root down, with
CLAUDE.local.md appended after CLAUDE.md in
each directory. Subdirectory CLAUDE.md files stay out of the window
until Claude reads a file in that subdirectory, which keeps the startup
load down and sets up a compaction trap I will get to.
The auto-memory index is capped on load: the first 200 lines or first
25KB of MEMORY.md, whichever comes first, with topic files
read on demand. The skill listing carries its own reserve:
skillListingBudgetFraction defaults to 0.01,
one percent of the model’s context window, and when the listing outgrows
that budget the descriptions of least-used skills get dropped while the
names stay. Git takes a slice too: includeGitInstructions
defaults to true and carries both the commit/PR workflow
instructions and a git status snapshot in the system prompt. When I want
to know which instruction files loaded and when, the
InstructionsLoaded hook logs it; when a monorepo’s nested
memory files are dead weight, claudeMdExcludes takes glob
patterns or absolute paths and trims them from the load (managed-policy
CLAUDE.md files are the exception, they cannot be excluded).
/context is where I check the ledger. It renders usage
as a colored grid with optimization suggestions and lists loaded memory
files under a Memory files heading. In fullscreen the per-item breakdown
collapses unless you run /context all. From v2.1.216 the
command also warns when a conversation has gone past the window and by
how far; my install reported 2.1.211 on 2026-07-27, so that warning is
not on this machine yet and I do the arithmetic off the grid.
Two moments call for it. I run /context after startup in
a repo whose instruction stack I have not audited, and again mid-session
when responses start referencing things nobody said. The grid answers
the first question a heavy session raises: is the weight in the
conversation, or in what loaded before the conversation began?
When compaction fires, and what it drops first
When context fills, Claude Code clears older tool outputs first, then summarizes the conversation. That order is the right one. Tool output is the cheapest thing in the window to lose, since a stale build log regenerates with one command. The conversation carries decisions, and decisions are what a summary is built to keep.
You can steer what survives. A “Compact Instructions” section in
CLAUDE.md sets standing rules for what the summary preserves;
/compact <focus instructions> steers a single pass.
Since v2.1.198 the summarization request inherits the session’s
extended-thinking configuration, so a session running with thinking on
summarizes with it on.
Auto-compaction ships enabled: autoCompactEnabled
defaults to true as of v2.1.119, shows in
/config as Auto-compact, and shuts off with the
DISABLE_AUTO_COMPACT environment variable set through the
env key. I leave it on. The alternative is a session that
hits the wall mid-task and stalls there.
The failure mode is documented and named. If a single oversized file or tool output refills the window after each summary, auto-compaction stops after a few attempts and surfaces a thrashing error instead of looping. The error is the tool refusing to burn tokens on a fight it cannot win. The fix belongs to the operator: find the artifact that keeps getting re-read and cut it out of the loop.
The trigger threshold is the number the docs decline to publish. The
context-window page describes compaction firing as the conversation
approaches the limit, in words rather than digits. A 200,000-token
figure does appear on that page, but inside its interactive simulation
code, labeled representative. The one hard threshold in the docs sits on
the Anthropic API: Sonnet 5 runs a 1M window there, has no 200K variant,
and auto-compacts at about 967K tokens by default, tunable through
CLAUDE_CODE_AUTO_COMPACT_WINDOW.
Window size itself is a choice on some models. Fable 5, Sonnet 5,
Opus 4.6 and later, and Sonnet 4.6 support a 1 million token window
selected through a [1m] model variant; Sonnet 5 runs at 1M
with no variant to pick. CLAUDE_CODE_DISABLE_1M_CONTEXT=1
strips the 1M options from the picker for anyone who wants the
temptation gone. A bigger window defers compaction. It does not repeal
it, and it raises the stakes on the cache math further down.
What survives compaction
| Mechanism | After compaction |
|---|---|
| System prompt and output style | Unchanged (not in message history) |
| Project-root CLAUDE.md and unscoped rules | Re-injected from disk |
| Auto memory | Re-injected from disk |
Rules with paths: frontmatter |
Lost until a matching file is read again |
| Nested subdirectory CLAUDE.md | Lost until a file there is read again |
| Invoked skill bodies | Re-injected, capped at 5,000 tokens per skill and 25,000 total, oldest dropped first |
| Startup skill listing | Not re-injected; invoked skills alone are preserved |
| Hooks | Unaffected; they run as code |
The table hides two traps. The first is the pair of on-demand mechanisms: path-scoped rules and nested subdirectory CLAUDE.md files drop out at compaction and stay out until a matching file is read again. Post-compact, the agent can edit a file whose local rules left with the summary. The rules return on the next read of a matching file, so the exposure is the stretch of work done from summarized knowledge alone, and that stretch has no fixed length.
The second trap is the skill listing. The startup listing is not
re-injected after /compact; skills that were invoked
survive, capped at 5,000 tokens each and 25,000 in total, oldest dropped
first. A session that was choosing skills off the menu loses the menu.
The skills it already used keep working, trimmed.
Hooks and the system prompt come through untouched. Of the rest,
project-root CLAUDE.md, unscoped rules, and auto memory come back from
disk, which is one more reason durable instructions belong in files
rather than in conversation: a rule stated in chat is subject to the
summarizer, a rule in .claude/rules/ is not.
Three resets, and where sessions persist
Three resets do three different jobs. /clear [name]
starts a new conversation with empty context; /reset and
/new are aliases, and since v2.1.191 the cleared
conversation can be restored from the rewind menu’s previous-session
entry, which removed the old finality. /compact keeps the
thread and trades detail for room. /rewind rolls the
conversation, the code, or both back to a checkpoint
(/checkpoint and /undo are aliases), and it is
the one of the three that touches files.
Sessions persist as plaintext JSONL under
~/.claude/projects/. --continue (short
-c) reopens the most recent conversation,
--resume (short -r) reopens by ID or name, and
both append to the same session ID. --fork-session on the
CLI, or /branch inside a session, copies the history into a
new session ID instead. Forks are how I test a risky direction against
accumulated context: the fork inherits the whole conversation and the
original stays clean, whatever the experiment does to the fork.
--session-id takes a UUID when a script needs a predictable
handle, and --no-session-persistence exists in print mode
for runs that should leave no transcript behind.
Housekeeping runs on a clock. cleanupPeriodDays defaults
to 30, the minimum is 1, and 0 is a validation error rather
than a synonym for keep-forever. The same setting governs startup
deletion of session files and of orphaned worktrees, so the retention
decision covers disk in two places at once.
Keeping sessions around costs close to nothing. The docs put
background functionality (the conversation summarization behind
claude --resume, status commands like /usage)
at a typical figure under $0.04 per session in tokens, idle
included.
What /usage
shows about session cost
/usage shows session cost, plan usage limits, and
activity stats; /cost and /stats are aliases,
with /stats opening on the Stats tab. It is the cost
surface I live on. On Pro, Max, Team, or Enterprise plans the breakdown
goes further: usage by skill, by subagent, by plugin, and by individual
MCP server, as percentages, with a flag on any behavior accounting for
10% or more of recent usage. That flag has one job, naming the thing
eating the plan. A misbehaving MCP server or an over-invoked skill shows
up as a line item instead of a hunch.
I am not printing one of my own readouts here. A cost table is the one figure in this book you should never take from someone else’s machine: yours will be shaped by your plan, your model, and which of your skills and servers are loud. Run it and read your own.
The screen has keyboard legs. d and w flip
between the last 24 hours and the last 7 days. r retries
when the plan-limits request is rate limited, and when the request fails
outright, bars cached within the past 60 minutes render under a
Showing last-known usage note, so a stale reading announces
itself as stale. As of v2.1.211 (which is what this machine runs),
session totals reset on /clear, making the number legible
per unit of work instead of per terminal lifetime.
Timing is a non-issue. /status, /tasks, and
/usage run without interrupting a response, while other
commands sent mid-response queue until the turn finishes. Checking the
meter mid-turn costs the glance and nothing else.
/usage-credits opens usage-credits billing settings in
the browser. Two documented edge behaviors: Team and Enterprise members
without billing access send a request to their admin from the CLI after
a confirmation dialog (v2.1.211+), and when no browser can open, over
SSH for instance, the command prints the URL instead (v2.1.205+). As a
solo operator I take the browser path and move on.
What
a scripted run costs: total_cost_usd and
--max-budget-usd
With --output-format json, a print-mode run returns
total_cost_usd, a per-model cost breakdown, a
session_id, and a modelUsage field reporting
the model that ran. That last field earns its keep when a fallback chain
is configured: the bill should name the model that did the work, not the
model that was asked for. Interactive sessions get a meter; scripted
ones get that envelope.
The cap runs the other direction. --max-budget-usd sets
a dollar ceiling and stops the session when the run reaches it. Anything
I schedule or loop gets one, because an unattended session with a retry
pattern and no ceiling is a blank check. The pair is the whole
discipline: cap before the run, read total_cost_usd after
it, compare.
Print mode also has its own context hygiene. --bare
skips auto-discovery of hooks, skills, plugins, MCP servers, auto
memory, and CLAUDE.md, and the docs state it will become the default for
-p in a future release. In bare mode the tools shrink to
Bash, file read, and file edit, and authentication must come from
ANTHROPIC_API_KEY or an apiKeyHelper inside
the JSON passed to --settings, since bare mode skips OAuth
and keychain reads. A scheduled one-shot does not need my interactive
session’s furniture, and every skipped instruction file is context the
run never pays for. On the input side there is a hard stop: piped stdin
into claude -p is capped at 10MB as of v2.1.128, and
exceeding it exits non-zero instead of stuffing the window.
Prompt caching and agent teams: the two cost multipliers
Two mechanisms move cost more than any slash command.
Prompt cache lifetime is the first. On a subscription the cache lives
one hour. Once a session draws on usage credits it drops to five
minutes, and five minutes is the default on an API key or a cloud
provider. The failure is the coffee break: a six-minute pause on an API
key means the next turn rebuilds the cache against the full window at
full price, and the deeper the session, the worse the rebuild. Long
sessions on a short cache lifetime want either sustained attention or a
deliberate close. Caching can be switched off outright with
DISABLE_PROMPT_CACHING, with per-family variants such as
DISABLE_PROMPT_CACHING_FABLE; I have not found a reason
to.
Agent teams are the second. They ship disabled, enable with
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1, and the costs page
attaches a number: about 7x more tokens than a standard session when
teammates run in plan mode. Seven times the tokens can be a fine trade
when the work splits well. It is a number to know before the run, not
one to discover on the invoice.
The routine I keep
Stripped to habits: /context at the start of any session
in an unfamiliar instruction stack, and again when the session gets
heavy. Compact Instructions in the CLAUDE.md of long-lived repos, so the
summary keeps what the repo cares about without me steering each pass.
Fork rather than resume when the next move is an experiment. A
--max-budget-usd ceiling on anything that runs without me
watching. /usage with the d toggle at the end
of the day, because the 24-hour view is where a misbehaving skill or MCP
server first shows its percentage. The meters cost nothing to read, and
I read them.