Skip to content

AI CODING ASSISTANTS

Claude Code Slow: The CLAUDE.md Cause Nobody Checks

A claude code slow session gets blamed on the model. The likelier cause is the one context source Claude Code loads with no size limit at all: your CLAUDE.md.

A claude code slow session usually gets blamed on the model or the network. Check your CLAUDE.md before either of those: it is the only context source Claude Code loads in full, with no size limit, at the start of every session, and re-injects after every /compact. This guide shows how to measure the context floor your project pays before you type a single character, using a script we wrote for this site's repository, and which fixes actually reduce it. Everything here was measured on Claude Code 2.1.222 against a real 135-line CLAUDE.md.

Key takeaways

  • CLAUDE.md has no truncation limit — Anthropic's documentation states it is "loaded in full regardless of length." Auto memory's MEMORY.md is the file that gets cut, at 200 lines or 25KB.
  • The 200-line guidance is a target you can exceed silently. Nothing warns you, and nothing truncates.
  • Splitting CLAUDE.md into @path imports does not reduce context, because imported files load at launch too. Path-scoped rules in .claude/rules/ do.
  • This repository's measured floor is 6,735 bytes across three sources before any prompt — roughly 1,683 tokens by a 4-chars-per-token estimate.
  • Whether file size hurts adherence is genuinely disputed; the token cost is not disputed at all, so optimise for that.

What actually makes a claude code slow session

Four things plausibly make Claude Code feel slow, and only one of them is worth checking first because only one is free to measure. Most claude slow reports name one of these without having checked which.

  • Model and effort level. A high reasoning effort spends thinking tokens before it answers. Real, but it is a setting you chose.
  • Tool round-trips. Every file read and shell command is another request carrying the whole conversation.
  • Cache misses. The expensive one, covered below.
  • The startup context floor. Everything loaded before you type. This is the one nobody checks, and it is the same cost on every single session.

The floor is where CLAUDE.md lives, and it compounds differently from the others: a slow tool call costs you once, while a bloated context file costs you on every request of every session for as long as the file stays that size. If you have not yet set one up deliberately, how to set up CLAUDE.md covers the structure; this article is only about what it costs you once it exists. For the wider picture of how the CLI assembles a session, see the complete Claude Code guide.

The one context source with no size limit

Here is the asymmetry that makes this worth an article. Claude Code has two persistent memory systems and they behave in opposite ways when they grow.

SourceBehaviour as it growsWarning when exceeded
MEMORY.md (auto memory)Truncated at 200 lines or 25KBYes — an error tells Claude to rewrite the index
CLAUDE.mdLoaded in full, no capNone

Anthropic's memory documentation is explicit about both halves. On auto memory: "The first 200 lines of MEMORY.md, or the first 25KB, whichever comes first, are loaded at the start of every conversation." And immediately after: "This limit applies only to MEMORY.md. CLAUDE.md files are loaded in full regardless of length, though shorter files produce better adherence."

So the file the tool writes for itself is capped and policed. The file you write by hand is uncapped and unpoliced. The widely-quoted "target under 200 lines" is guidance, not enforcement — cross it and nothing at all happens, visibly.

That contested point matters for how you should read the rest of this. The token floor is arithmetic and you can verify it in a minute. The claim that a big file makes Claude dumber is not settled, so this guide does not lean on it. See CLAUDE.md mistakes that slow Claude Code down for the evidence on both sides.

Measure the context floor before you type

/context inside a session lists which memory files loaded, which is the right tool for "did this file load at all." It is not the right tool for "what does this project cost me on every session," because it reports on a session already in progress and does not break the startup floor down by file. Measured across 89 sessions, that floor averaged 40,668 tokens against a session peak of 266,267 — the gap, and what closes it, is what happens to the context window once it starts filling.

So we wrote a script that does. It walks the files Claude Code loads at launch, applies the documented truncation rules per source, and prints the total. The interesting part is that the rules differ per source, so the script cannot treat them uniformly:

scripts/check-context-weight.mjs
// Auto memory's index is truncated on load; everything else loads in full.
let loadedChars = content.length;
let truncated = false;
if (opts.truncateAt) {
  const byLine = lines.slice(0, MEMORY_LINE_LIMIT).join("\n");
  const capped = byLine.length > MEMORY_BYTE_LIMIT ? byLine.slice(0, MEMORY_BYTE_LIMIT) : byLine;
  truncated = capped.length < content.length;
  loadedChars = capped.length;
}

It also strips what Claude Code strips. Block-level HTML comments are removed before CLAUDE.md is injected into context, so counting the raw file overstates the cost:

scripts/check-context-weight.mjs
function loadedContent(raw) {
  // Block-level HTML comments are stripped before CLAUDE.md enters context.
  return raw.replace(/^[ \t]*<!--[\s\S]*?-->[ \t]*\r?\n?/gm, "");
}

That detail is a small free win on its own: maintainer notes inside <!-- --> in a CLAUDE.md cost nothing, because they never reach the model.

What the measurement found in this repo

Run against this site's repository, on Claude Code 2.1.222:

Our verification run
node scripts/check-context-weight.mjs
# → SOURCE                                      LINES    BYTES  ~TOKENS
# → project CLAUDE.md                             135     6157     1539
# → auto memory MEMORY.md                           1      141       35
# → skill write-article (frontmatter only)          5      437      109
# →   ↳ body 3085 B loads only on /write-article
# → TOTAL (always loaded)                                 6735     1683

Three things in that output are worth reading carefully.

The token column was an estimate, and the estimate was wrong. It divided characters by four, a heuristic rather than a tokeniser, and the script printed a disclaimer under every run for that reason. It has since been measured: Markdown tokenises at about 3.4 bytes per token, not four, so the divisor undercounted this floor by roughly 17%. The figures in this section are the ones the script printed on 2026-08-06 at the old divisor and are left as they were captured; our later runs use the corrected divisor.

The skill body is not in the floor. .claude/skills/write-article/SKILL.md is 3,085 bytes of instructions, and only its 437-byte frontmatter counts at startup — the body loads when the skill is invoked. That is the entire architectural argument for moving instructions into skills rather than growing CLAUDE.md, expressed as a number: 437 bytes always, versus 3,522 bytes always if the same content lived in CLAUDE.md.

135 lines is under the target and still dominates. CLAUDE.md is 91% of this project's floor. It is not a bloated file by any standard, and it is still the whole problem, which is what makes "just keep it under 200 lines" weaker advice than it sounds.

For a before-and-after on the same file, this repository's own history has one. Commit 38d964a removed two rules that had gone stale against the code:

Our verification run
git show 38d964a^:CLAUDE.md | wc -lc
# → 138    6309
git show 38d964a:CLAUDE.md | wc -lc
# → 135    6199

Three lines and 110 bytes — about 27 estimated tokens per session. On its own that is nothing, and pretending otherwise would be dishonest. The point is the direction of travel: a file that is never trimmed only grows, and the cut that matters is the one that removes a whole section rather than three lines.

Three bugs the script hit first

The measurement was wrong three times before it was right, and each failure is a trap worth naming because two of them fail silently.

The auto-memory directory was never found. Claude Code derives the per-project directory name from the working directory path, and D:\Projects\Devventa becomes D--Projects-Devventa — two dashes, because the drive colon and the separator each become one. The first version collapsed the run:

The bug
const repoSlug = repoRoot.replace(/[:\\/]+/g, "-");  // → D-Projects-Devventa

One dash, no match, no error. The script cheerfully reported a total that omitted auto memory entirely. A missing file and an empty directory look identical to existsSync, so nothing failed.

A one-line file reported as truncated. Stripping the trailing newline for the line count but not for the byte comparison made capped.length < content.length true by exactly one character, so a 141-byte MEMORY.md printed a warning that it exceeded a 25KB limit.

The output leaked an absolute home path. This is the one that mattered, because the script's output was always going to be quoted in an article. The path logic assumed path.relative() returns a .. prefix when a file sits outside the repo — true on a single-volume layout, false on Windows across drives, where it returns a full absolute path instead:

scripts/check-context-weight.mjs
function displayPath(file) {
  const norm = (p) => p.split(path.sep).join("/");
  const inRepo = path.relative(repoRoot, file);
  if (inRepo && !inRepo.startsWith("..") && !path.isAbsolute(inRepo)) return norm(inRepo);
  const inHome = path.relative(homedir(), file);
  if (inHome && !inHome.startsWith("..") && !path.isAbsolute(inHome)) return `~/${norm(inHome)}`;
  return path.basename(file);
}

The isAbsolute check is the fix. The general lesson is narrower than it looks: a measurement script that prints paths is a redaction surface, and on Windows the cross-drive case is the one that catches you.

Why splitting CLAUDE.md does not help

The instinct when a context file gets long is to break it up. Whether that works depends entirely on which mechanism you reach for, and the two obvious ones do opposite things.

  • @path imports do not help. Anthropic's memory docs are unambiguous: imported files "are expanded and loaded into context at launch alongside the CLAUDE.md that references them," and splitting "helps organization but doesn't reduce context, since imported files load at launch." You get tidier files and an identical bill.
  • Path-scoped rules do help. A file in .claude/rules/ with a paths: frontmatter key only enters context when Claude reads a matching file. This is the actual deferral mechanism.
  • Skills help most. A skill contributes its frontmatter at startup and its body only when invoked — the 437-versus-3,085-byte split measured above.
  • HTML comments are free. Block-level <!-- --> comments are stripped before injection.
.claude/rules/mdx-authoring.md
---
paths:
  - "content/**/*.mdx"
---

Write string attributes on every MDX component prop: width="1200", never width={1200}.

Rules like that one load when Claude opens an MDX file and cost nothing on a session spent in lib/. The script reports path-scoped rules separately and excludes them from the always-loaded total, which is the distinction that makes the number honest.

Where the time actually goes

Reducing the floor by a few hundred tokens will not, on its own, make a slow claude session feel fast. Searches for claude code too slow usually come from a session that has been open for hours rather than from a fresh one, and that distinction matters: it is worth being precise about where the time in a long session really goes, because two of the three causes are about context size and one is not.

Cache misses are the expensive event. Claude Code re-sends the conversation with every request and relies on prompt caching to make that affordable. Anthropic's cost documentation states that "your first message after a break longer than the cache lifetime misses the cache and reprocesses your full context," and that the lifetime "is an hour on a subscription and drops to five minutes once you're drawing on usage credits." Every byte in your startup floor is in that reprocessed prefix, and it is reprocessed on each miss.

Compaction re-reads everything. /compact "reads the conversation it summarizes," so compacting a large context is itself a large request. Project-root CLAUDE.md is then re-injected from disk afterwards — a design that keeps your instructions alive across compaction, and also means the file is paid for again.

Long sessions cost more regardless. A one-line question in a session that has been open all day still carries the whole conversation. Bucketing 20,168 billed turns by session depth put a number on that: turns 1–10 averaged 44,278 input tokens and turns past 250 averaged 382,549, an 8.6x climb with no change in the work — the measurement is in where a Claude Code bill actually comes from. /clear between unrelated tasks is the highest-leverage habit here and it costs nothing — and approving a plan can clear that research context for you.

Set expectations accordingly. Trimming CLAUDE.md is a real fix for a real cost, and it is a smaller lever than clearing context between tasks. When a claude code slow session is the complaint, do both, and do the free one first.

We later put a clock on that expectation across 270 timed turns, and it is worth knowing before you spend an afternoon on the floor: per-step latency barely moves with context size at all (r = 0.077), while turn duration tracks how many steps the turn takes (r = 0.801). Trimming is a cost fix, and the speed fix is a different lever. If your slowdown is physical host lag rather than API latency, check your Claude Code memory usage and ensure unignored directories are not thrashing V8 garbage collection.

How long a turn takes, timed

The transcripts under ~/.claude/projects carry a stopwatch that is easy to miss, because assistant turns hold a usage object and no timing field. Enumerating every system subtype rather than the expected ones turns up this:

A turn_duration record, verbatim
{
  "type": "system",
  "subtype": "turn_duration",
  "durationMs": 281759,
  "messageCount": 288,
  "version": "2.1.228",
  "timestamp": "2026-08-12T10:19:24.908Z"
}

durationMs is the wall-clock time of a whole user turn — your prompt to the agent finishing, every model round-trip and tool call inside it. messageCount is the session's running total, not a per-turn count, so the delta between consecutive records is how many steps that turn cost; dividing by the raw total produces a fictional per-step figure that shrinks as sessions get longer. We found 270 of these records across 17 versions and 89 sessions between 2026-08-06 and 2026-08-28 — a sample against more than twenty thousand billed turns, not a census.

Turn durationValue
10th percentile2.5s
Median3m 08s
75th percentile17m 31s
90th percentile37m 23s
99th percentile1h 32m
Longest2h 42m

Divide through by steps and the picture stabilises: the median turn is 44 steps, and one step costs 3.7 seconds at the median and 6.6 at the 90th percentile, whether it is step three or step three hundred. Two correlations over the same 270 records then answer the speed question directly:

RelationshipPearson r
Turn duration against steps in that turn0.801
Per-step latency against session context size0.077

Context governs what a turn costs; step count governs what a turn takes. Sessions here ranged from around 40,000 to well over 300,000 tokens of mean context per turn, so there was real variation to detect, and none of it showed up in step latency — 98.7% of the context on a turn is cache reads, and a cached prefix is cheap to serve as well as to bill. So the speed levers are about steps:

  • Scope the request so the agent does not have to discover the task. A prompt that names the file, the function and the intended change removes search steps.
  • Ask for one thing. A turn that does three things runs the loop three times as many rounds.
  • Tell it where to look. Every file the agent must open to find out is another step at about 3.7 seconds.

Where the wall clock goes

Eight sessions in the same corpus record their own time split. Across 5h 53m of elapsed time, 38.1% was in the API, 15.4% executing tools, and 46.5% was neither — rendering, queueing, and the time between the agent finishing and you reading its work and typing the next thing. Retries were 0.03% of API time, so the client quietly re-sending failed requests is not why a session feels long.

Nor is the release. Per-step latency across the versions with at least ten timed turns ran from 3,764ms on 2.1.237 to 6,198ms on 2.1.226 with no trend, and the newest measured version, 2.1.247, sat mid-range at 4,089ms. Measuring context cost per version found the same shape — the most expensive release was three weeks old — so if a session feels slower after an upgrade, the upgrade is still the least likely explanation.

None of this benchmarks the model: turn duration includes tool execution, file I/O, the network and whatever else the machine was doing, and the 3.7-second step is one machine on one connection. The shape — duration tracking steps, latency ignoring context — is the part we would expect to travel.

Best practices for a lean context floor

  • Measure before you cut. Weigh the files on disk, or at minimum run /context, and find out which file is actually the problem. In this repo it was 91% one file, which no amount of guessing would have told you.
  • Move procedures to skills, not to imports. A multi-step workflow belongs in a skill that loads on invocation. An import is a filing decision, not a cost decision.
  • Scope rules by path when they only apply to part of the tree. MDX rules for content/**, database rules for lib/.
  • Delete rather than reorganise. The /doctor checkup, on Claude Code 2.1.206 and later, proposes trims for a checked-in CLAUDE.md and specifically cuts what Claude can derive from the codebase itself — directory layouts, dependency lists, architecture overviews.
  • Keep pitfalls, drop descriptions. The content worth its tokens is what differs from the tool's defaults: the trap, the reason, the convention Claude would otherwise get wrong.
  • Put maintainer notes in HTML comments. They are stripped before injection, so they are genuinely free.

Common mistakes

  • Assuming the 200-line target is enforced. It is not. Nothing truncates CLAUDE.md and nothing warns you. The symptom is a file that has quietly reached 400 lines while you assumed a limit was protecting you. Fix: measure it, on a schedule.
  • Splitting into imports to "reduce context." Tempting because the file gets shorter and the directory looks organised. The symptom is no change at all in token usage. Fix: use path-scoped rules or skills, which actually defer.
  • Trusting a grep to find your config. The mistake made while building the script for this article: a path derived by pattern rather than verified against the filesystem. The symptom is a total that silently omits a source. Fix: assert the file exists and fail loudly when it does not.
  • Quoting token counts as measurements. A chars-per-token heuristic is fine for comparing two versions of the same file and useless as an absolute figure. Fix: label estimates as estimates, and compare bytes when precision matters.
  • Trimming context to go faster. Tempting because it does reduce something real. The symptom is a leaner setup that feels exactly as slow. Fix: count steps per turn instead — duration tracked them at r = 0.801.
  • Blaming the model first. The cheapest checks are the startup floor and /clear. Both are free and take a minute; changing models is neither.
  • Blaming the last upgrade. The release is the most legible thing that changed, so it collects the blame for the instruction set that grew alongside it. We later joined 20,157 billed turns to the version that produced each one and found the most expensive version was three weeks old: what actually changed when Claude Code got slow after an update. Fix: measure both variables over the same window before rolling anything back.

Conclusion

Treat a claude code slow session as a context question before a configuration one, and spend the first minute there. Run /context, confirm which files loaded, and measure what your project pays before you type — in this repository that floor was 6,735 bytes, 91% of it a single CLAUDE.md that was already inside the recommended size. Then fix it in the order that pays: /clear between unrelated tasks, procedures into skills, path-scoped rules for anything that only applies to part of the tree, and imports for nothing at all. Next, read CLAUDE.md mistakes that slow Claude Code down for what belongs in the file once you have made room. If you want to understand how long multi-turn sessions trigger rolling lockouts, see AI coding rate limits. And if the file is already lean but its rules keep getting skipped, that is a different failure with a different fix — why Claude ignores CLAUDE.md covers the diagnosis order.

Frequently asked questions

Does a long CLAUDE.md make Claude Code slower?
It raises the token floor of every session, which is measurable. Whether it degrades response quality is contested: Anthropic's memory documentation states longer files reduce adherence, while a factorial study across 1,650 Claude Code sessions found no detectable adherence effect from file size. Treat the context cost as real and the adherence cost as unproven.
Is there a size limit on CLAUDE.md?
No. Anthropic's documentation states CLAUDE.md files are loaded in full regardless of length. The 200-line figure is a target, not a cap. Auto memory's MEMORY.md is the file that actually gets truncated, at 200 lines or 25KB whichever comes first, with everything past the limit dropped on the next load.
Does splitting CLAUDE.md into imports reduce context?
No. Imported files are expanded and loaded into context at launch alongside the CLAUDE.md that references them, so @path imports help organisation and change nothing about token cost. Path-scoped rules in .claude/rules/ are the mechanism that genuinely defers loading, because they only enter context when Claude reads a matching file.
How do I see what loaded into my session?
Run /context and read the Memory files list. It shows which CLAUDE.md and CLAUDE.local.md files actually loaded, which is the fastest way to catch a file you thought was being read and isn't. For a per-file byte and token breakdown before you start a session, measure the files on disk instead.
Why does a claude slow session get worse the longer it runs?
Claude Code sends the full conversation with every request, so a long session carries more each turn. The sharp edges are cache misses and compaction: a first message after a break longer than the cache lifetime reprocesses the whole context, and /compact reads the conversation it summarises, making it a large request in its own right.
How do I make Claude Code faster?
Reduce the number of steps a turn takes, not the size of your context. Across 270 timed turns, turn duration correlated with step count at r = 0.801 and per-step latency correlated with session context at r = 0.077 — essentially not at all. A turn that runs 44 steps takes about 44 times one step, and trimming context does not shorten a step.

Muhammad Kashif

Founder and editor of Devventa, covering AI coding assistants, Next.js and the modern AI development stack.