{"slug": "my-claude-md-was-48000-tokens-i-cut-it-to-4400-and-then-measured-whether-that", "title": "My CLAUDE.md was 48,000 tokens. I cut it to 4,400 — and then measured whether that broke anything.", "summary": "A developer restructured a 194,492-character CLAUDE.md memory file down to 17,283 characters — a 91% reduction — by splitting content into a resident core plus 12 skills and 28 reference files loaded on demand, then built a measurement harness that runs real headless Claude Code sessions to verify the restructure did not break behavior. All 26 security rules and 21 architectural invariants stayed resident, while topic-specific guidance moved to skills and path-globbed rules. The developer reports only four words lost, all from headings deleted intentionally.", "body_md": "Every session in my repo started by loading a 194,492-character `CLAUDE.md`. That is roughly **48,600 tokens, on every single message**, and about 4.9× the size at which Claude Code starts warning you about a memory file.\n\n**Disclaimer:** This guide and the accompanying scripts were generated with\n\nthe assistance of AI. The workflow was tested end-to-end on one real\n\nrepository and the numbers quoted throughout are from that run, but every\n\ncodebase differs — please read and double-check all terminal commands and\n\nscripts before running them on your own system.\n\nTwo things worth knowing before you start. **The restructure rewrites\n`CLAUDE.md` in place** and creates files under `.claude/` — commit or back up\n\nfirst so you can `git checkout` your way out. **The measurement harness launches real headless Claude Code sessions**, one per prompt, which consume\n\nAPI quota and can take several minutes each; start with two or three prompts\n\nbefore running twenty.\n\nNone of it was junk. It was two years of hard-won operational memory: the migration command that silently deletes an index, the seat index that retries the same seat forever, the colour function that serialises to a syntax both our contrast checkers were blind to. Deleting it was not an option.\n\nI got it to **17,283 characters — a 91% reduction — with four words lost, all of them from headings I deleted on purpose.** Then I measured whether the restructure actually worked, which turned out to be the most useful part and the part nobody writes about.\n\nHere is the whole method, including the mistake I made halfway through.\n\n**\"Just delete the stale bits.\"** You cannot know which bits are stale without re-reading the code each one describes. \"This trap looks obsolete\" is a guess, and a wrong guess deletes the one note that would have saved the next debugging session.\n\n**\"Summarise it.\"** This is the dangerous one, and I will come back to it.\n\n**\"Use `@import` to split it up.\"** Imports expand inline. You get a tidier file and identical context cost.\n\nThe actual problem is not that the file is big. It is that **all of it loads for all of it**, including the 90% irrelevant to whatever you are doing right now. A CSS bug does not need the Stripe webhook ordering rules.\n\n| Layer | Holds | Loads | \n|---|---|---|\n| `CLAUDE.md` | Commands, and rules true in **every** session | Always | \n| `.claude/skills/<name>/SKILL.md` | One topic — an index if large | When its description matches the task | \n| `.claude/skills/<name>/references/*.md` | The detail behind one part of a skill | When the skill reads it | \n\nThere is a fourth, underused mechanism: **`.claude/rules/*.md` with `paths:` frontmatter**, which loads by glob match on the files you touch. No model judgment involved — if the path matches, it loads.\n\nI ended with a 17KB `CLAUDE.md`, 12 skills, and 28 reference files.\n\nA skill loads when the model judges its description relevant. That judgment is very good, but it is a judgment. So the test is not \"is this important?\" — everything in the file was important. The test is:\n\n**If this rule failed to load exactly when it mattered, how bad is that?**\n\nA styling trap: you lose some time. A security rule: you ship the bug it exists to prevent.\n\nSo in my file, every security rule stayed resident — all 26 of them — along with 21 architectural invariants (one database per tenant, the append-only ledger, the partial unique index arbitrating seat contention) and the commands, which are needed constantly and are cheap.\n\nEverything else — fees, booking, notifications, the mobile apps, the rendered pages, the test practices, the known-gaps list — moved.\n\nTo state 47 rules in a ~200-line file, the obvious move is to rewrite each into a crisp one-liner.\n\n**Do not do this.** My own repo's guidance, written by past-me after getting burned, says it better than I can:\n\nA stale claim that gets edited without being checked is laundered, not corrected. Re-read the code the line describes, or leave the line alone.\n\nParaphrasing 47 rules I had not re-verified would produce 47 confident-sounding claims nobody checked — and they would look *freshly reviewed*, which is worse than being obviously old.\n\nThe way out: **your rules are probably already written with the rule as the opening sentence.** Mine were. So lift that sentence **verbatim** and move the reasoning to a reference file. Nothing is rewritten. The rule sits in `CLAUDE.md`; the \"why\" sits one hop away.\n\n```\n- **The card never touches this server.** A SetupIntent hands the browser a\n  client secret and Stripe's own SDK collects the number, which is why no\n  endpoint here takes one.\n```\n\nOne sentence resident. The remaining 400 words of reasoning in `hard-rules/references/security.md`.\n\nTwo of my entries opened with a *finding* rather than an instruction (\"A capital letter moved an anonymous crawler onto a studio's members' rate bucket\"). Both happened to end with the author's own \"The general rule: …\" sentence. I carried that too — still their words, not mine.\n\nLine-by-line diffing is useless: moving prose re-wraps it, so every line reads as changed. Sentence matching is nearly as bad, because bold markers and headings shift the boundaries.\n\n**Word multisets are immune to both.** Every word of the original must still appear, at least as many times, somewhere in the new corpus:\n\n``` python\nimport collections, re\nWORD = re.compile(r'[A-Za-z0-9_./:%-]+')\na = collections.Counter(WORD.findall(old_text))\nb = collections.Counter(WORD.findall(all_new_text))\nlost = {w: a[w] - b[w] for w in a if a[w] > b[w]}\n```\n\nMy final run: **4 of 31,697 word occurrences missing** — `Traps`, `bitten`, `Invariants`, `simplify`. All four from the two section headings I deleted. That is what done looks like.\n\nAnd check that it *fails* when it should. I deleted one skill directory and re-ran: 2,522 losses, including the word `fee` forty times. A verification you have never seen fail is not a verification.\n\nMy build script *appended* the moved entries to two existing skill files. I ran it twice while iterating.\n\nResult: 36 entry blocks where 28 belonged. Silent duplication — the exact failure mode every guide on this warns about, and I walked into it anyway while writing about avoiding it.\n\nIt was caught only because I counted each entry across every file and asserted it appeared exactly once. **Add that check.** Make your build script idempotent, or have it rewrite files rather than append to them.\n\nHere is where I diverge from the other write-ups.\n\nA widely-shared figure says skill activation runs around **79%** versus 100% for a rule sitting directly in `CLAUDE.md`. That is a genuinely useful warning, and it is also **one person's measurement of their own configuration**. It is not a constant. I had been repeating it as if it were.\n\nSo I measured mine. The method:\n\n`claude -p`). The session doing the restructure already has every skill in context; asking it \"would you have loaded this?\" measures nothing.`session_id` via `--output-format json` so probes can run in parallel without being confused with each other.`Edit`/` Write` so the measurement cannot modify the repo it measures.\nCount **two** signals, not one: the `Skill` tool firing, *and* the model reading a skill file directly. Reaching the right knowledge by opening the file is a hit.\n\n**Result: 13 of 14 — 93%.** Not 79%. And on every single hit, the skill fired as **tool call #1**, before any file was opened.\n\nThe one miss still produced a **correct answer**. It just rebuilt it from scratch: **56 tool calls, against a median of 16 on the hits.**\n\nIt re-read four source files and grepped the API to re-derive what the skill would have handed it in one read. Meanwhile the always-loaded layer acted as a safety net — a resident hard rule about calendar dates routed it to the right reference anyway.\n\nThat reframes the whole risk. A miss is not \"the rule does not get applied.\" It is a session that costs 3.5× more. That is a much less frightening failure mode, and I would never have known it from reading blog posts, including this one.\n\nBoth mobile prompts hit the same skill. Only the one containing the word \"Expo\" fired.\n\nThe skill's description advertised **tooling**: *\"SDK pinning against the Expo Go ceiling, Metro under pnpm, driving a simulator with idb.\"* But the skill also held screen-level traps — a Book button offered on a class the member already holds, a crashing date field. The prompt *\"the Booked screen crashes\"* matched none of the advertised words.\n\n**Write descriptions that name symptoms, not tools.** What will the user actually *see*?\n\n`MODULE_NOT_FOUND`, a missing query engine, an image far bigger than expected, or a fix that only takes effect on the second run\"\nI rewrote all 11 descriptions this way and re-measured with seven fresh symptom-phrased prompts — the exact style that had failed.\n\n**7 of 7, every one at tool call #1.** The prompt that previously missed now hits, in 26 tool calls instead of 56.\n\nFor anything that maps onto a directory, `.claude/rules/*.md` removes the judgment entirely:\n\n```\n---\npaths:\n  - \"apps/mobile/**\"\n  - \"apps/mobile-yoga/**\"\n---\n\nYou are in one of the two Expo member apps.\n\n**Read `.claude/skills/mobile-apps/SKILL.md` before changing anything here.**\n```\n\nKeep these as **pointers, never copies** — the knowledge stays in exactly one place.\n\n**Verify with a two-sided canary.** A scoped rule matching nothing looks identical to one that works:\n\n`paths: [\"some/dir/**\"]` whose body says to emit a unique token.\nStep 3 is the one people skip. Without it you cannot tell \"works\" from \"always loaded\", and the second silently undoes your savings. Mine passed both ways: 1 and 0.\n\nOne warning: build the globs against your **real** layout. I assumed `apps/api/src/fees/`; the code actually keeps fees, memberships and class packs in `apps/api/src/billing/`. Had I not checked, the rules would have matched nothing, silently, forever.\n\n**The corpus grew 14%** — 194,492 to 220,885 characters. That extra 26,393 is frontmatter, index tables and pointers. None of it is knowledge. It exists so the right file can find you.\n\n**A change now lands in more places.** One file became three edits. More places to go stale. A routing table in `CLAUDE.md` helps; it is a mitigation, not a guarantee.\n\n**Occasional forgetfulness.** You trade \"always slightly bloated\" for \"usually lean, occasionally expensive.\"\n\nI would make the trade again. But state it plainly rather than pretending the restructure is free.\n\nI packaged the method as a Claude Code plugin — two skills: one that performs the restructure with the safety rules baked in, one that measures whether your skills actually fire.\n\n```\n/plugin marketplace add dhondooo/claude-md-progressive-disclosure\n/plugin install progressive-claude-md\n```\n\nThen just say: *\"my CLAUDE.md is too big, restructure it\"*, and later, *\"measure whether my skills actually fire.\"*\n\nThe three-layer framing and the 79% activation warning come from [a note.com post on progressive disclosure](https://note.com/jolly_daphne9092/n/n5fc43e6541f0). The `<150` line SKILL.md convention, the `references/` layer and the \"what becomes what\" mapping are from [cem.karaca's write-up](https://medium.com/@cem.karaca/my-claude-md-was-eating-42-000-tokens-per-conversation-heres-how-i-fixed-it-85ffba809bd4). The memory tiers, `@import` caveat and compression passes are from [bijit211987's guide](https://medium.com/@bijit211987/the-complete-guide-to-claude-md-memory-rules-loading-and-cross-tool-compression-97cc12ed037b). Shyam Verma's [\"Stop overfeeding your CLAUDE.md\"](https://shyamverma.com/stop-overfeeding-your-claude-md) is where the \"<50% of conversations → make it a skill\" heuristic comes from.\n\nWhat I would add to all of them: **measure your own hit rate before building anything to fix it.** Mine was 93% before I touched a thing, and the one real defect was a sentence I had written badly — not the architecture.", "url": "https://wpnews.pro/news/my-claude-md-was-48000-tokens-i-cut-it-to-4400-and-then-measured-whether-that", "canonical_source": "https://dev.to/dhondooo/my-claudemd-was-48000-tokens-i-cut-it-to-4400-and-then-measured-whether-that-broke-anything-pho", "published_at": "2026-09-15 17:20:46+00:00", "updated_at": "2026-09-15 17:49:25.049892+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "large-language-models", "ai-agents"], "entities": ["Claude Code", "Anthropic"], "alternates": {"html": "https://wpnews.pro/news/my-claude-md-was-48000-tokens-i-cut-it-to-4400-and-then-measured-whether-that", "markdown": "https://wpnews.pro/news/my-claude-md-was-48000-tokens-i-cut-it-to-4400-and-then-measured-whether-that.md", "text": "https://wpnews.pro/news/my-claude-md-was-48000-tokens-i-cut-it-to-4400-and-then-measured-whether-that.txt", "jsonld": "https://wpnews.pro/news/my-claude-md-was-48000-tokens-i-cut-it-to-4400-and-then-measured-whether-that.jsonld"}}