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.

Day 9: Writing Rules Claude Actually Follows

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 any types 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:

  1. Visual distinction from regular rules
  2. Signals maximum importance
  3. 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.