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.