DEV Community

jidonglab
jidonglab

Posted on

Claude Code /compact Forgot 31% of My Rules: 20 Trials, 1 Fix

Forty minutes into a refactor, I told Claude Code, for the third time that session, "we use pnpm, not npm." It said "Got it." Then the context filled up, auto-compact kicked in, and the very next tool call was npm install.

A fresh package-lock.json landed next to my pnpm-lock.yaml. I wasn't even annoyed at first. I was curious. What exactly does Claude Code /compact keep, and what does it quietly throw away?

So I stopped guessing and ran the same session 20 times.

TL;DR

  • Across 20 compaction trials, 74 of 240 rule checks (31%) failed after Claude Code /compact. The model either forgot the rule or broke it on the next action.
  • Rules in CLAUDE.md survived 100% of the time (0 of 80 lost). They get reloaded from disk, so they never depend on the summary.
  • Rules said only in chat lost 46%, and "don't do X" rules were hit hardest: 61% gone.
  • /compact with custom instructions cut the loss to 11%. It helped, but it didn't fix it.
  • A SessionStart hook with the compact matcher that re-injects a constraints file brought losses to 0 of 80 in 10 more trials.

What does Claude Code /compact actually do?

Claude Code /compact replaces your conversation history with a model-written summary, then keeps going from that summary. Auto-compact does the same thing on its own once the context window gets close to full.

The summary is good at what happened: files touched, the current task, the last error. It's worse at what you asked it never to do. A summary is a story about the session, and "don't touch the migrations folder" isn't an event. It's a standing order, and standing orders don't fit naturally into a story.

That was my hypothesis. Here's how I tested it.

The setup: 12 rules, 20 compactions

One real TypeScript monorepo, a mid-sized Express API plus a React client. Every trial followed the same script:

  1. Start a fresh Claude Code session.
  2. Give it 12 rules over the first ~30 minutes of real work. 4 lived in CLAUDE.md. 8 came up naturally in chat, the way I'd normally say them.
  3. Keep working until the context was heavy, then run /compact with no arguments.
  4. Probe every rule two ways: ask about it directly, and give a task that would break it if forgotten.

A rule counted as lost if the model got the question wrong or broke the rule during the probe task. Behavior mattered more than recall. Several times it recited the rule correctly and then broke it anyway.

The 8 chat rules, roughly as I said them:

# Rule Type
1 Use pnpm, not npm positive
2 Postgres runs on port 5433 positive
3 Tests go in __tests__, not next to source positive
4 Use the logger wrapper for all logging positive
5 Don't edit anything in db/migrations/ negative
6 Don't add new dependencies without asking negative
7 Don't touch legacy/billing.ts, another team owns it negative
8 Don't run the e2e suite, it hits staging negative

20 trials × 12 rules = 240 checks.

Which rules does Claude Code forget after compaction?

The rules that got lost were almost always ones stated only in conversation, and prohibitions were lost far more often than instructions. Here's the full breakdown:

Where the rule lived Checks Lost Loss rate
CLAUDE.md 80 0 0%
Chat, positive ("use X") 80 25 31%
Chat, negative ("don't X") 80 49 61%
Total 240 74 31%

Three patterns showed up again and again.

1. Prohibitions evaporate. "Don't touch legacy/billing.ts" was the worst single rule. It was lost in 15 of 20 trials. When I read the summaries, the file was often mentioned ("reviewed legacy/billing.ts for the invoice bug") but the ownership warning was gone. The summary kept the noun and dropped the "no."

2. Rules said once, early, lose to rules said often. pnpm came up three times in a normal session and survived more often than port 5433, which I said exactly once in minute four. Repetition acts like a vote in the summary.

3. Recall and behavior drift apart. In 11 of the 74 failures, the model answered the direct question correctly ("yes, we use pnpm") and then ran npm anyway two steps later. If you only test by asking, you'll overestimate how much survives.

The worst behavior failures, the ones that would actually hurt:

  • npm install run after compaction: 6 of 20 trials
  • An edit to a file in db/migrations/: 4 of 20
  • The e2e suite started against staging: 3 of 20 (I had it pointed at a dead URL, thankfully)

Does /compact with custom instructions fix it?

Partly. Claude Code lets you pass focus instructions: /compact Preserve every user rule and constraint verbatim. I ran 10 more trials with exactly that line.

Chat-rule loss dropped from 46% to 11% (9 of 80 checks). That's a big improvement, and it costs nothing. But it has two problems:

  • It only works when you run /compact. Auto-compact doesn't know about your instruction, and auto-compact is exactly when you aren't paying attention.
  • 11% is still one broken rule every trial or so. The remaining losses were, again, mostly the "don't" rules.

I also tried putting the focus instruction in CLAUDE.md ("when compacting, preserve all user rules"). It had no measurable effect in 5 trials, so I dropped it.

Why do CLAUDE.md rules survive compaction?

CLAUDE.md rules survive because they aren't part of the conversation. Claude Code loads them from disk as project memory, so they never have to make it through the summary. The summarizer can't drop what it was never asked to compress.

That points straight at the fix: move anything that must survive out of the chat and onto disk.

The fix: a SessionStart hook on compact

Stuffing every session-specific rule into CLAUDE.md gets messy fast. Some rules only matter for today's task. So I split them:

  • Permanent rules → CLAUDE.md
  • Session rules → .claude/constraints.md, which I (or Claude) append to as they come up

Then a hook re-injects the constraints file right after every compaction. Claude Code's SessionStart event accepts a compact matcher, and whatever the command prints to stdout is added to the model's context:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "cat \"$CLAUDE_PROJECT_DIR/.claude/constraints.md\""
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

That goes in .claude/settings.json. The constraints file is boring on purpose:

# Active session constraints (re-read after every compaction)
- Package manager: pnpm. Never npm or yarn.
- Postgres: port 5433.
- DO NOT edit db/migrations/. Ask first.
- DO NOT add dependencies without asking.
- DO NOT touch legacy/billing.ts (owned by payments team).
- DO NOT run the e2e suite (targets staging).
Enter fullscreen mode Exit fullscreen mode

I ran 10 more trials with the hook active and plain auto-compact (no custom instructions). Result: 0 of 80 chat-rule checks lost. No npm, no migration edits, no e2e runs.

To make adding rules painless, I put one line in CLAUDE.md: "When I state a constraint for this session, append it to .claude/constraints.md." Claude did that without prompting in most sessions. When it didn't, I just said "add that to constraints."

What this cost and what I'd caveat

The whole experiment took about two evenings. Most of that was the 30-minute warm-up per trial, which I partly scripted by replaying the same prompts. The hook itself took five minutes.

Caveats, honestly:

  • One repo, one model version, 40 total trials. The compaction prompt and the model behind it change between Claude Code releases. Your numbers will be different. I'd bet the shape holds: prohibitions go first.
  • My probes were adversarial by design. Every probe task was built to tempt a rule break. Normal work tempts less often, so the real-world hit rate is lower than 31%.
  • The hook adds tokens after every compaction. My file was about 120 tokens. If yours grows to 3,000, you're paying that after every compact and you've rebuilt CLAUDE.md with extra steps. Prune it when the task ends.

So, does Claude Code /compact forget your rules?

Yes. In my 20 trials, Claude Code /compact lost 31% of the rules I'd given it, and 61% of the "don't do X" rules I'd only stated in chat. Rules in CLAUDE.md were never lost, because they're reloaded from disk instead of summarized. Custom /compact instructions cut the loss to 11% but don't help with auto-compact. The fix that got me to zero is to keep session rules in a file and re-inject it with a SessionStart hook using the compact matcher. If a rule has to survive compaction, it shouldn't live only in the conversation.


Written by the developer behind Preterview, an interview prep platform.

Top comments (0)