Day 9: Writing Rules Claude Actually Follows
Learn how to write CLAUDE.md rules Claude Code actually follows instead of ignores. Direct language, specific thresholds, and verification methods included.
Hey, it's G.
Day 9 of the Claude Code series.
I had rules in my CLAUDE.md that Claude Code kept ignoring.
Not because it couldn't read them — because I wrote them badly.
Vague rules get interpreted loosely. Weak language gets skipped. Passive phrasing gets treated as optional.
Today I learned the difference between rules that stick and rules that don't.
It all comes down to how you write them.
The Problem (My Rules Weren't Working)
Here's what was in my old CLAUDE.md:
## Rules
- Use TypeScript properly
- Keep things organized
- Don't install random packages
- Git stuff should be careful
What I expected: Claude follows these rules consistently.
What actually happened:
- Claude used
anytypes everywhere - Created 300-line components
- Installed packages without asking
- Pushed to git without confirming
Why?
Every single one of those rules is vague, passive, or unenforceable.
"Use TypeScript properly" — What does "properly" mean?
"Keep things organized" — Organized how?
"Don't install random packages" — What's random? What's acceptable?
"Git stuff should be careful" — What does careful mean?
Claude had no idea what I actually wanted.
The Concept (Rules Are Language, Not Commands)
Here's the key insight:
Claude Code is not a robot executing commands literally.
It's a language model — it interprets your rules the same way it interprets everything else: through language.
That means how you phrase a rule determines how seriously it gets treated.
Think of it like writing a linter rule.
A linter doesn't care about feelings. It doesn't interpret "should" or "try to" or "probably."
It enforces exact conditions.
Write your CLAUDE.md rules the same way.
The Four Mistakes That Make Rules Get Ignored
Mistake 1: Too Vague
Example:
- Write clean code
- Keep components small
- Use best practices
Why this fails:
"Clean code" means different things to different people. "Small" is subjective. "Best practices" is a meaningless phrase.
Claude has no specific behavior to follow.
Mistake 2: Too Passive
Example:
- It would be nice if components stayed under 200 lines
- Probably don't install packages without checking
- Try to use named exports when possible
Why this fails:
"It would be nice" = optional
"Probably don't" = not a hard rule
"Try to" = do it if convenient
Passive language signals "this doesn't really matter."
Mistake 3: No Consequence Defined
Example:
- Don't use `any` in TypeScript
Why this fails:
No explanation of why it matters. No consequence. Easy to rationalize breaking.
"This one time it's fine because..."
Mistake 4: Buried in a Wall of Text
Example:
Some general guidelines for the project include using TypeScript
which we prefer with strict mode enabled and also we should try
to avoid using the any type when possible unless there's a really
good reason and components should generally be kept relatively
small and organized...
Why this fails:
Critical rules hidden in rambling paragraphs = invisible to Claude.
Important rules need to stand out.
The Four Fixes (How To Write Rules That Stick)
Fix 1: Be Specific
Instead of vague:
- Keep components small
Be specific:
- Never create a component longer than 150 lines. Split it.
Now Claude knows:
- Exact threshold (150 lines)
- What to do when exceeded (split it)
- No room for interpretation
Fix 2: Use Direct Language
Instead of passive:
- It would be nice if you asked before installing packages
- Try to use named exports
- Probably avoid inline styles
Be direct:
- Always ask before installing any dependency. No exceptions.
- Named exports only — never `export default`
- Never use inline styles — Tailwind utility classes only
Power words:
- Always
- Never
- Must
- Do not
- No exceptions
These signal non-negotiable rules.
Fix 3: Add the Why
Without why (easy to rationalize skipping):
- Don't use `any` in TypeScript
With why (harder to ignore):
- Never use `any` in TypeScript. This project uses strict mode
and `any` breaks type safety across the entire codebase.
Or even stronger:
- Never use `any` in TypeScript — no exceptions. Every `any`
bypasses type checking and creates potential runtime bugs that
TypeScript is supposed to prevent.
The why reinforces the rule.
Fix 4: Use Formatting for Critical Rules
For your most important rules, use emphasis:
## Behavior Rules
⚠️ NEVER run database migrations without explicit approval
⚠️ NEVER push to git without my instruction
⚠️ NEVER modify /lib/supabase.ts — ask first, always
- Ask before installing dependencies
- Run freely: npm run dev, npm run build, npm run lint
The warning symbols (⚠️) and CAPS make critical rules visually stand out.
These rules almost never get broken.
Weak vs. Strong Rules (Side-by-Side)
Example 1: Component Size
❌ Weak:
- Try to keep components small
✅ Strong:
- Components must be under 150 lines. If longer, split before committing.
Example 2: Package Installation
❌ Weak:
- Probably don't install packages without checking
✅ Strong:
- Always ask before installing any new dependency. No exceptions.
Example 3: TypeScript
❌ Weak:
- Use TypeScript properly
- Avoid any types when possible
✅ Strong:
- TypeScript strict mode — never use `any`, ever. No exceptions.
- Every function must have explicit return types.
Example 4: Git Operations
❌ Weak:
- Git stuff should be careful
✅ Strong:
⚠️ NEVER push to git without my explicit instruction
⚠️ NEVER run git commit without showing me the diff first
Advanced Pattern: Conditional Rules
For nuanced situations, use conditional rules:
- If adding a new route, always add it to /app and follow the
existing folder naming pattern
- If a component needs more than 3 props, create a TypeScript
interface for them — never inline the types
- If unsure whether a change is in scope — stop and ask.
Don't assume and proceed.
These give Claude clear decision trees:
"If X situation, then Y action"
Real Example (My Actual Before/After)
Before (Weak Rules Claude Ignored)
## Rules
- Use TypeScript properly
- Keep things organized
- Don't install random packages
- Git stuff should be careful
- Components should be readable
- Tests are good to have
Total: 6 vague, useless rules.
After (Strong Rules Claude Follows)
## Coding Conventions
- TypeScript strict mode — never use `any`, ever. No exceptions.
- One component per file. No exceptions.
- Named exports only — never `export default`
- Components over 150 lines must be split before committing
- Every function needs a descriptive name — no f(), temp(), x()
## Behavior Rules
⚠️ NEVER install a new dependency without asking me first
⚠️ NEVER run any git push without my explicit instruction
⚠️ NEVER modify any file in /lib without flagging it first
- Ask before running database migrations
- Run freely without asking: npm run dev, build, lint, typecheck
- If the scope of a task is unclear — ask before touching anything
The difference in Claude's behavior?
Before: Claude did whatever seemed reasonable.
After: Claude follows every rule consistently. Asks when unclear. Stops before critical operations.
How To Test Your Rules
After writing rules, verify Claude actually understood them.
Run this test:
claude
> What are the rules around TypeScript in this project?
Claude should respond with something specific:
TypeScript rules:
- Strict mode, never use `any` — no exceptions
- Explicit return types on all functions
- Named exports only, never default exports
Then test behavior rules:
> What do you need my approval for before acting?
Claude should respond:
I need approval before:
- Installing any new dependency
- Running database migrations
- Modifying any file in /lib
- Running git push
- Any unclear scope changes
And confirm autonomous behavior:
> What can you do without asking me first?
Claude should respond:
I can run without asking:
- npm run dev
- npm run build
- npm run lint
- npm run typecheck
If the answers match what you wrote — your rules are working.
If Claude hedges or gives vague answers — that rule needs to be rewritten.
The Warning Symbol Pattern (⚠️)
I use this for rules I absolutely cannot afford Claude to break:
⚠️ NEVER run database migrations without explicit approval
⚠️ NEVER push to git without my instruction
⚠️ NEVER modify /lib/supabase.ts — ask first, always
Why it works:
- Visual distinction from regular rules
- Signals maximum importance
- Stops Claude from rationalizing exceptions
These rules almost never get broken.
Even when Claude could technically proceed, the warning symbol makes it pause and ask.
Common Patterns That Work
Pattern 1: Never/Always + Action + Why
Never use `any` in TypeScript — this breaks type safety across
the entire codebase and defeats the purpose of strict mode.
Pattern 2: Must/Do Not + Specific Threshold
Components must be under 150 lines. If longer, split into
smaller components before committing.
Pattern 3: If X Then Y (Conditional)
If a function has more than 3 parameters, create a TypeScript
interface for them — never inline complex parameter types.
Pattern 4: Ask Before + Specific Action
Always ask before installing any new dependency — even if it
seems harmless or commonly used.
What NOT to Do
Don't Write Aspirational Rules
❌ Bad:
- We write comprehensive tests for everything
Reality: You have zero tests.
What happens: Claude writes tests you don't maintain.
✅ Good:
- No tests yet — we'll add them later. Focus on working features.
Or if you're adding tests:
- Every new utility function in /utils must have a matching test
in /utils/__tests__
Don't Use Wishy-Washy Language
❌ Avoid:
- "Should"
- "Try to"
- "Preferably"
- "It would be nice if"
- "When possible"
- "Generally"
- "Usually"
✅ Use:
- "Always"
- "Never"
- "Must"
- "Do not"
- "Required"
- "No exceptions"
Don't Bury Critical Rules
❌ Bad:
Some general thoughts on the project: we use TypeScript with
strict mode and we should avoid using any and also please don't
push to git without asking and components should be kept small...
✅ Good:
## Critical Rules
⚠️ NEVER push to git without permission
⚠️ NEVER use `any` in TypeScript — strict mode, no exceptions
⚠️ NEVER create components over 150 lines — split them
My Raw Notes (Unfiltered)
This was the aha moment for me.
I had rules that sounded reasonable but Claude kept doing things differently.
Rewrote them to be direct and specific and the behavior changed immediately.
The warning symbols (⚠️) for critical rules is a small thing but it works — those rules almost never get broken.
Asking Claude to recite the rules back is the best test. If it sounds fuzzy when it explains them back to you, rewrite the rule.
The "add the why" pattern is underrated. Claude seems to take rules more seriously when it understands the consequence.
Tomorrow (Day 10 Preview)
Topic: Evolving Your CLAUDE.md Over Time — how to keep it updated as your project grows so it never goes stale.
What I'm testing: How often should you update CLAUDE.md? When do rules need refinement? How do you know when a rule isn't working anymore?
Following This Series
Phase 2 (Days 8-14): Getting Productive ⬅️ You are here
So far in Phase 2:
- Day 8: CLAUDE.md structure and sections
- Day 9: Writing rules Claude actually follows (today)
- Day 10: Evolving CLAUDE.md over time (tomorrow)
G
P.S. - The single biggest improvement after restructuring my CLAUDE.md: rewriting vague rules to be direct. Same file, drastically different behavior.
P.P.S. - If Claude keeps breaking a rule, the problem isn't Claude. The problem is how you wrote the rule. Make it more specific and more direct.
P.P.P.S. - Test every rule by asking Claude to explain it back to you. If the explanation is vague or hedged, rewrite the rule.