← All recipes
  • claude-md
  • project-setup
  • cost

Should your CLAUDE.md describe the code, or just the decisions? We tested 3 versions

Same task, same planted trap, 9 runs: no CLAUDE.md, a 9-line decisions-only file, and an 84-line full description. Sonnet avoided the trap and passed the hidden test in all 9. The decisions file was the cheapest at $0.051 a run; the full description cost $0.073, 43% more.

Cost per run Each bar is the average cost of the runs, starting at $0.

See every run, check and diff →

After our 7-line CLAUDE.md test, @TheDjedMethod told us how they write theirs: “One short file per repo, but only the things it can’t work out from the code: decisions, the reason behind them, and what not to do again.” The other common style is the long file that describes the whole codebase. We put both against no file at all, on a repo with one planted mistake.

The setup

A small Node pricing service, fx-pricing, with no dependencies. It has two caching helpers:

  • memoize() in src/lib/memo.js caches results forever. Three files in the repo use it, for static lookup tables (categories, shipping zones, VAT).
  • cached(fn, { ttlMs }) in src/lib/ttl-cache.js caches results for a set time. Nothing in the repo uses it yet.

The trap: the obvious, most-used pattern is memoize(), and it’s wrong for exchange rates, because a memoized rate never refreshes. The prompt:

Add a convert(amountCents, from, to) function in src/fx.js that converts an amount in integer cents from one currency to another, using the rate from fetchRate() in src/providers/fx-provider.js, rounded to whole cents. Cache the rates so we don’t call the provider on every conversion. Add a test for it.

Three setups, 3 runs each:

  • No CLAUDE.md.
  • Decisions only (9 lines): provider rates go through cached(), never memoize(), with a 10-minute TTL for FX, and why (“in March 2026 memoized FX rates stayed stale for a day and we under-charged EU orders”); memoize() is only for static tables; money is integer cents rounded with roundCents(); no npm dependencies; run npm test before you’re done.
  • Full description (84 lines): commands, the file layout, how the modules fit together, money rules, conventions, dependencies and currencies. The same caching decision, word for word, sits in a “Caching” section in the middle.

Both files are in the runner overlays (decisions-only and full-description).

Six checks graded every run afterwards. The two that answer the question: src/fx.js doesn’t use memoize(), and a hidden test Claude never saw. It sets a rate, converts, changes the rate, converts again a minute later (must still use the cached rate and not call the provider), then converts again 12 minutes after the first call (must pick up the new rate). It also checks rounding to whole cents. The others: npm test passes, no npm dependencies added, a test for convert was added, and the tests ran after the last edit.

Claude Code 2.1.312, Sonnet (claude-sonnet-5-5), claude -p.

What happened

No CLAUDE.md Decisions only (9 lines) Full description (84 lines)
Runs that avoided the trap 3 of 3 3 of 3 3 of 3
Runs passing the hidden test 3 of 3 3 of 3 3 of 3
Runs passing all 6 checks 3 of 3 3 of 3 3 of 3
Average cost per run $0.057 $0.051 $0.073
Versus no CLAUDE.md -10.6% +28.1%
Cost range $0.056 to $0.057 $0.049 to $0.053 $0.058 to $0.089
Average tokens per run 118,009 103,800 132,073
Average output tokens 2,333 1,946 3,201
Starting context, tokens 18,379 18,791 19,915
Average turns 7.3 8.7 13.7
Cache TTL Claude chose 5 minutes (3 of 3) 10 minutes (3 of 3) 10 minutes (3 of 3)

3 runs per column (averages unless marked). Token counts include cached input. Every run, check and diff: run data.

What we learned

The trap didn’t catch Sonnet, even with no file. All 9 runs used cached() from ttl-cache.js. None used memoize(). With no file, Claude opened both memo.js and ttl-cache.js in all 3 runs and picked the one with a TTL. With either file, it never opened memo.js at all. So on this repo, the warning in the CLAUDE.md wasn’t what kept Claude out of the trap.

What only the files could tell it was the number. With no file, Claude guessed a 5-minute TTL in all 3 runs. With either file, it used the house value, 10 minutes, in all 6. Our hidden test accepts both, so that difference doesn’t show up as a fail. In a real repo, a guessed number like that is exactly the kind of thing a decisions file is for.

The decisions file was the cheapest setup. $0.051 a run, 10.6% less than no file, with the fewest tokens (103,800 a run). It added about 412 tokens to the starting context. We don’t know for sure why it came in under no file; with 3 runs a setup, the gap is about half a cent.

The full description cost the most, and Claude did more of what it described. $0.073 a run, 43% more than the decisions file, with the most turns (13.7 on average) and output tokens. In all 3 runs it also exported convert from src/index.js, which the long file asks for (“Anything new callers need goes there too”) and the others never did. That’s arguably the right call, but you pay for every instruction you add, in context (about 1,536 extra starting tokens here) and in the extra work they trigger.

What failed, and the limits of this test

  • Nothing failed. All 9 runs passed all 6 checks, so this test can’t tell you which file stops a repeated mistake better. It only shows that neither file hurt, and what each one cost.
  • The trap was too easy for Sonnet. In our first smoke run, the repo already used cached() for shipping quotes with a TTL in config.js, and Claude copied it. We removed that hint before the scored runs, and Sonnet still avoided the trap. A repo where the wrong pattern is the only one in sight, or a weaker model, might split the setups.
  • One repo, one task, 3 runs per setup. The cost gaps are cents.
  • Two smoke runs. Both used no CLAUDE.md, $0.052 and $0.056, before the 9 scored runs ($0.542). Total: $0.65.
  • API cost only. We measured dollars and tokens at API prices, not how much of a Pro or Max plan’s weekly limit the same work would use.

The recipe

  1. Write down the decisions, not the codebase. Claude reads the code fine. On this test, a 9-line file of decisions and reasons was the cheapest setup and passed every check.
  2. Put the numbers and the “why” in it. The TTL was the one thing no-file runs got differently. Values that live in someone’s head, and the incident behind a rule, are what the code can’t tell Claude.
  3. Write “never X” next to the thing to use instead. Our line named both memoize() and cached() and said which one is for what.
  4. Treat every extra line as an instruction. The 84-line file made Claude do more work it described, for 43% more than the short one. If you wouldn’t want Claude to act on a line every time, cut it.