Skip to content
Underdog Diary

I Explained the Same Rule to Claude Five Times. Here's Why It Kept Doing Its Own Thing.

Every session starts empty. Your rules arrive as a plain message, not a strict config. The model reads them and tries to comply. There's no guarantee.

  • Lesson 06
  • Intermediate
  • 6 min
  • Updated: August 2026

Before this: Lesson 5. You need the tool installed for this to mean anything.

You told Claude the same thing for the fifth time, and it still did it its own way. Today we're breaking down how its memory actually works — and why your rules keep getting ignored.

Every Session Starts Empty

Claude doesn't remember yesterday. It doesn't learn from your project over time, and it keeps nothing between runs. The only thing that carries over between sessions are files. You write the rules. Claude manages its own memory on top of that.

And here's the part nobody tells you upfront: your rules arrive as a plain message, like you typed them into the chat yourself. It's not a strict config. Claude reads it and tries to follow it. There's no guarantee it will.

Here's how that actually sits on disk:

~/.claude/
├── CLAUDE.md        global habits
├── rules/           personal rules
└── projects/         auto-memory
 
my-app/
├── CLAUDE.md         project rules
├── CLAUDE.local.md   yours, gitignored
└── .claude/
    ├── rules/        topic-based rules
    ├── skills/       procedures
    └── settings.json hooks

Two Folders Named .claude, Two Different Jobs

Same name, different scope. The one in your home directory applies to every project on your machine. The one inside a project only applies there.

The question that comes up most: if one project uses fully rounded corners and another uses a sharper 40% radius, the design system lives inside that project. The next project over has no idea it exists.

CLAUDE.md: Where Your Habits Actually Live

In your home folder, this file holds your personal habits — response language, package manager of choice, things you never want Claude to do.

At the root of a project, it holds run/test commands, code style, architectural decisions, and the gotchas. This file ships with the repo — the whole team gets it. Next to it sits CLAUDE.local.md: same idea, but yours only — local ports, test user, staging link, gitignored.

# My Project
 
## Commands
npm run dev — server on 5173
npm run build — build to dist
npm run typecheck — type check
 
## Rules
Don't add dependencies without asking.
Don't touch the migrations folder.
 
## Gotchas
Port 6006 is taken by another project —
run Storybook on 6007 instead.

That "Gotchas" section is the most valuable part of the whole file. Claude can figure out your commands on its own by reading package.json. It will never guess that port 6006 is taken — it'll hit that wall every single session until you write it down.

Files stack on top of each other. If two of them give contradicting instructions, Claude picks one. Silently. No warning, no ask.

200 lines is the usual recommendation — but a 400-line file performs worse, not better. The rules just get buried in noise. Test every line with one question: "if I delete this, will it start making mistakes?" If the answer's no, cut it.

Topic Rules: Split It Up

Anything that's bloating your main file belongs in .claude/rules instead. One file for your design system, one for database work, one for testing.

You can attach a rule to a mask — load it only when a file inside a specific folder is open. Claude decides that on its own: it checks which file is open and pulls in the matching rule.

The mask lives in the file's header, above the actual rule. ** matches any nested folders, * matches anything in a filename.

---
name: layout
description: Layout rules
paths:
  - "src/**/*.tsx"
  - "**/*.stories.tsx"
---
 
# Layout Rules
 
The actual rule text goes here.

Important catch: the trigger is opening a file, not creating one. If Claude builds a new component from scratch without opening anything first, your design system never gets loaded. So any rule that needs to apply when creating files should skip the mask entirely.

Skills: For Procedures, Not File Types

A skill is just a folder with a SKILL.md inside. At startup, only the name and description load into context — the actual body loads later, when it's needed.

---
name: deploy
description: Ship a release to production. Use when asked to deploy, ship, or roll back a version.
---
 
# Shipping a Release
 
The step-by-step process goes here.

This is where the model itself makes the call. It sees the description and decides whether to open the folder. Which means the description is the trigger — write it specifically, using the same words someone would actually type. A vague description like "about releases" won't get it opened.

Skills are for procedures: deploys, incident reviews, release builds. Anything that isn't tied to a specific file type.

Hooks: The One Thing Claude Can't Skip

Everything above can be ignored. A hook can't.

A hook is a command that fires automatically on an event — no model involved. Run the linter after every edit. Check file size. Block writes to the migrations folder entirely.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/lint.sh"
          }
        ]
      }
    ]
  }
}

Reading it: PostToolUse means after the tool finishes running. matcher sets which tools trigger it — here, file writes and edits. command is what actually runs — a plain shell script you write yourself.

One detail that trips people up: the path to your script goes through a variable, and that matters. $CLAUDE_PROJECT_DIR is your project root — but the hook itself executes from the current working directory. Those two aren't guaranteed to match, and a relative path will point somewhere wrong if they don't.

Auto-Memory: What Claude Writes Down on Its Own

These are files Claude manages by itself. To save something, just say it in chat: "remember that tests need a local Redis instance."

Here, the 200-line cap is a hard wall, not a suggestion. Anything past it doesn't load at all — a 400-line file is half-invisible.

Setting This Up From Scratch

Run /init to get a draft project file. Trim it with the same test as before: "if I remove this line, will it start erring?" Anything tied to a specific part of the codebase goes into rules. Anything procedural goes into skills. Anything that has to run no matter what goes into a hook. Anything personal goes into .local.

Then run /context. It shows you what's actually loaded into the session. If a file isn't in there, Claude isn't seeing it — no point guessing why the rule isn't working.

One rule to end on: write it down the second time you explain something. Not the first. The first time might just be a fluke.

New lessons, when they’re ready

No schedule, no drip campaign. I send one when I've actually learned something worth writing down.