Claude Code Tips: 7 Rules That Make It 10x More Useful
Claude Code is powerful out of the box. But without rules, it drifts. It forgets your conventions, generates inconsistent patterns, and makes you repeat yourself every session.
I've been using Claude Code daily for six months. These are the 7 rules that turned it from "impressive demo" into "indispensable teammate."
1. Give It Context via CLAUDE.md
The single most impactful thing you can do is create a CLAUDE.md file in your project root. Claude Code reads it automatically at the start of every session. No API calls, no configuration menus — just a markdown file.
Without CLAUDE.md:
You: "Add a new API endpoint for user profiles"
Claude: *generates Express.js code when your project uses Fastify*
With CLAUDE.md:
# CLAUDE.md
## Stack
- Runtime: Node.js 20
- Framework: Fastify 4
- ORM: Drizzle
- Database: PostgreSQL 15
## Conventions
- All route handlers go in `src/routes/`
- Use Zod schemas for request/response validation
- Return consistent `{ data, error, meta }` response envelopes
Now Claude Code generates Fastify routes with Drizzle queries and Zod validation on the first try. No corrections needed.
Time saved: 30+ minutes per day of "no, we use Fastify, not Express" corrections.
2. Prevent Drift with Explicit Boundaries
Claude Code is eager to help. Sometimes too eager. It'll refactor code you didn't ask about, add error handling you don't need, or "improve" patterns that were intentional.
Add this to your CLAUDE.md:
## Rules
- Keep changes focused and minimal. Do not refactor unrelated code.
- Don't add features, error handling, or improvements beyond what was asked.
- Don't add comments or docstrings to code you didn't change.
- One logical change at a time.
Before this rule:
You: "Fix the null check in getUserById"
Claude: *fixes the null check, refactors the entire file to async/await,
adds JSDoc to 8 functions, renames 3 variables*
After this rule:
// Only the null check changes. Nothing else.
export function getUserById(id: string) {
const user = users.get(id);
if (!user) {
throw new NotFoundError(`User ${id} not found`);
}
return user;
}
This one rule alone eliminated 80% of my unwanted diffs.
3. Use File-Scoped Rules for Different Contexts
Here's something most developers miss: you can place CLAUDE.md files in subdirectories. Claude Code merges them hierarchically — root rules apply everywhere, subdirectory rules apply only in that scope.
project/
CLAUDE.md # Global rules
src/
api/
CLAUDE.md # API-specific rules
frontend/
CLAUDE.md # Frontend-specific rules
tests/
CLAUDE.md # Test-specific rules
Your src/api/CLAUDE.md might say:
## API Rules
- All endpoints must validate input with Zod schemas
- Always return proper HTTP status codes (don't default to 200)
- Include rate limiting middleware on public endpoints
While src/tests/CLAUDE.md says:
## Test Rules
- Use Vitest, not Jest
- Always test the happy path AND at least one error case
- Use factory functions for test data, never raw objects
- Integration tests hit real database, never mock the DB layer
This means Claude Code writes strict, validated API code but flexible, factory-driven test code — without you saying anything each session.
4. Understand CLAUDE.md vs System Prompt
This is the most common point of confusion. They're different mechanisms:
| System Prompt | CLAUDE.md | |
|---|---|---|
| Scope | Single API call | Every session in the project |
| Persistence | Ephemeral | Git-versioned file |
| Visibility | Only the API caller | Entire team |
| Hierarchy | Flat | Directory-scoped |
| Access | Requires API access | Just edit a file |
The key insight: System prompts control Claude's behavior in API integrations. CLAUDE.md controls Claude Code's behavior in your development environment. If you're using Claude Code (the CLI/IDE tool), CLAUDE.md is what you want.
System prompts are still relevant if you're building applications that call the Claude API. But for day-to-day coding with Claude Code, CLAUDE.md is the persistent, team-shared, version-controlled way to shape behavior.
5. Add Auto-Formatting Rules
Nothing wastes more time than Claude Code generating code in one style and your linter immediately flagging it. Tell Claude your formatting expectations upfront:
## Formatting
- Use single quotes for strings in TypeScript
- 2-space indentation
- No semicolons (Prettier handles it)
- Trailing commas in multiline arrays/objects
- Prefer `const` over `let`, never use `var`
- Destructure function parameters when there are 3+ args
Before:
function createUser(name: string, email: string, role: string, team: string) {
var user = {
"name": name,
"email": email,
"role": role,
"team": team
};
return user;
}
After:
function createUser({
name,
email,
role,
team,
}: {
name: string
email: string
role: string
team: string
}) {
const user = { name, email, role, team }
return user
}
Zero linting errors. Zero reformatting. Code goes straight from Claude into your codebase.
6. Control Test-Writing Behavior
Left unchecked, Claude Code writes tests that are either too shallow (just testing that the function exists) or too complex (mocking everything, testing implementation details). Be specific:
## Testing
- Test behavior, not implementation
- Each test should break if the feature breaks, pass if it works
- No snapshot tests unless explicitly requested
- Prefer integration tests over unit tests for API routes
- Use `describe` blocks grouped by feature, not by function name
Before (implementation-testing):
test('calls database.query with correct SQL', () => {
const spy = jest.spyOn(database, 'query');
getUsers();
expect(spy).toHaveBeenCalledWith('SELECT * FROM users');
});
After (behavior-testing):
describe('getUsers', () => {
test('returns all active users', async () => {
await seedUsers([
{ name: 'Alice', active: true },
{ name: 'Bob', active: false },
]);
const users = await getUsers({ active: true });
expect(users).toHaveLength(1);
expect(users[0].name).toBe('Alice');
});
});
The first test breaks when you refactor the SQL. The second test only breaks when the feature actually breaks. That's the difference.
7. Add a "Never Do This" Section
Positive rules tell Claude what to do. But the highest-leverage rules are often the negative ones — the things you've been burned by before:
## Never
- Never use `any` type in TypeScript — use `unknown` if uncertain
- Never delete or modify migration files
- Never commit .env files or secrets
- Never use `console.log` for error handling — use the logger
- Never add dependencies without checking if we already have an equivalent
- Never use default exports
This section is your project's institutional memory. Every time Claude Code does something that costs you time, add a rule. Over weeks, the "never" section becomes the most valuable part of your CLAUDE.md.
Start Building Your Rules Today
These 7 rules transformed Claude Code from a tool I had to babysit into one I trust to generate production-ready code. The key is being specific, being persistent (via CLAUDE.md), and iterating on your rules as you discover new friction points.
If you want a head start, I put together a complete CLAUDE.md Rules Pack — battle-tested rules for TypeScript, Python, React, API development, and more. It's the exact rule set I use daily, organized by project type so you can drop it in and start getting better output immediately.
Get the CLAUDE.md Rules Pack ($27) ->
What rules have you added to your CLAUDE.md? Drop them in the comments — I'm always looking for new patterns to steal.
Top comments (0)