How Should You Configure CLAUDE.md for Better AI Coding?
Problem
I had a “sensible CLAUDE.md.” I wrote comprehensive rules about null safety, type handling, and code quality. I thought I was covered.
Then I reviewed my codebase after a month of AI-assisted development.
src/ services/ UserService.ts - 12 null checks, 3 implicit undefined PaymentService.ts - 8 null checks, 2 implicit undefined OrderService.ts - 15 null checks, 5 implicit undefined utils/ validators.ts - 7 null checks, scattered null/undefinedThe codebase was accumulating technical debt. Too many undefined/nulls allowed in parameters and structure fields. Too many null checks sprinkled over the codebase.
My CLAUDE.md clearly stated:
# Null Safety
- Always define strict types- Never use implicit undefined- Handle null cases explicitly- Avoid optional chaining when you can check explicitlyThe rules were there. But the code didn’t follow them.
What happened?
I found a Reddit discussion comparing OpenCode vs ClaudeCode. Someone described the exact same problem I had.
The key insight from that thread was devastating: “A small set of rules in CLAUDE.md is good, but without strict safety net using conventional code analysis tools is not enough.”
I realized my mistake. I was treating CLAUDE.md as a complete solution, not one layer of a defense system.
The problems were:
Problem 1: AI Memory Constraints
Even with explicit rules, AI assistants don’t consistently apply every guideline across all changes. Sometimes Claude would be strict about types. Other times it would let any slip through.
Problem 2: No Enforcement Mechanism
Rules written in natural language lack the strict enforcement that code analysis tools provide. CLAUDE.md rules are suggestions, not requirements.
Problem 3: Single-Agent Blind Spots
When the same agent writes and reviews code, it has blind spots. It assumes its own code is correct because it followed its own interpretation of the rules.
One commenter on Reddit put it directly: “Stating coding requirements alone is not enough - need separate reviewer agent.”
How I fixed it
I rebuilt my approach with three layers.
Layer 1: Focused CLAUDE.md
I trimmed my CLAUDE.md. Instead of trying to cover everything, I focused on what tooling can’t catch:
# Coding Standards
## Null Safety (CRITICAL)
ALWAYS define strict types, NEVER allow implicit undefined:
```typescript// WRONG: Implicit undefinedfunction processUser(user: User) { return user.name // What if user is undefined?}
// CORRECT: Explicit null handlingfunction processUser(user: User | null): string { if (!user) { throw new Error('User is required') } return user.name}After Every Change
- Run
npm run lintbefore committing - Run
npm run typecheckto catch type errors - Review the diff for unintended null/undefined introductions
The key difference: I added an "After Every Change" section. This triggers Claude to run tooling after modifications.
### Layer 2: Automated Tooling
I configured ESLint to enforce what CLAUDE.md describes:
```javascript title=".eslintrc.js"module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/explicit-function-return-type': 'warn', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/strict-boolean-expressions': 'error', }}I also added a custom ESLint rule for import boundaries. The Reddit commenter mentioned “enforcing who can import what”:
module.exports = { rules: { 'import-boundaries': { meta: { type: 'problem' }, create(context) { return { ImportDeclaration(node) { const source = node.source.value const filename = context.getFilename()
// Prevent utils from importing from components if (filename.includes('/utils/') && source.includes('/components/')) { context.report(node, 'Utils cannot import from components') } } } } } }}Now when Claude generates code that violates these rules, ESLint catches it immediately. No interpretation needed.
Layer 3: Separate Reviewer Agent
This was the missing piece. I configured a dedicated reviewer agent:
## Code Review
When code is written or modified, use **code-reviewer** agent for final verification.The reviewer checks:- Null/undefined introductions- Type safety violations- Import boundary violations- Test coverageThe workflow changed:
User request → Claude writes code → Done (violations slip through)
vs
After (multi-agent)User request → Claude writes code → code-reviewer agent checks → Fix issues → DoneWhy this works
I ran a comparison over two weeks.
Week 1: CLAUDE.md only
Day 1: 3 null checks, 2 implicit undefinedDay 3: 5 null checks, 1 implicit undefinedDay 5: 4 null checks, 3 implicit undefinedDay 7: 7 null checks, 2 implicit undefinedTotal: 19 null checks, 8 implicit undefined introducedWeek 2: Three-layer approach
Day 1: 1 null check (caught by ESLint before commit)Day 3: 0 violations (ESLint blocked 2, reviewer caught 1)Day 5: 0 violations (ESLint blocked 1)Day 7: 0 violationsTotal: 1 null check, 0 implicit undefined introducedThe numbers tell the story. Rules without enforcement are suggestions. Rules with tooling and separate review become requirements.
The three layers work together:
Consistency: Automated tools enforce rules every time, without exception. Claude can’t “forget” to check null safety when ESLint blocks the commit.
Reduced Cognitive Load: Claude focuses on logic rather than remembering every style rule. The linter handles style. Claude handles business logic.
Catching Edge Cases: Static analysis catches issues that even experienced developers miss. The code-reviewer agent catches what ESLint can’t (like logical issues in null handling).
Common mistakes I made
Mistake 1: Overloading CLAUDE.md
# Code Quality
- Use meaningful variable names- Keep functions under 50 lines- Avoid deep nesting- Write comments for complex logic- Handle errors gracefully- Validate all inputs- Use const over let- Prefer arrow functions- Avoid magic numbers- ... (30 more rules)When everything is important, nothing is important. Keep CLAUDE.md focused on what tooling can’t enforce.
Mistake 2: No enforcement mechanism
Rules without automated checking are suggestions:
# Null Safety
Always define strict types. Never use implicit undefined.// .eslintrc.jsrules: { '@typescript-eslint/strict-boolean-expressions': 'error', '@typescript-eslint/no-implicit-any': 'error',}Mistake 3: Single-agent workflow
Using one agent for both coding and review:
Claude writes code → Claude reviews own code → Claude approves(Clause assumes it followed its own rules)Claude writes code → code-reviewer agent checks → Fixes applied(Independent agent finds issues the coding agent missed)Mistake 4: Ignoring tool integration
I didn’t leverage ESLint plugins for specific issues:
module.exports = { extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', ], rules: { '@typescript-eslint/no-unnecessary-condition': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', }}The complete setup
Here’s my final configuration:
CLAUDE.md (focused rules + workflow triggers)
# Coding Standards
## Null Safety (CRITICAL)
ALWAYS define strict types, NEVER allow implicit undefined.
## After Every Change
- Run `npm run lint` before committing- Run `npm run typecheck`- Review diff for null/undefined introductions
## Agent Orchestration
When code is written/modified → Use **code-reviewer** agentWhen starting new feature → Use **tdd-guide** agentESLint (automated enforcement)
module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/explicit-function-return-type': 'warn', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/strict-boolean-expressions': 'error', '@typescript-eslint/no-unnecessary-condition': 'error', }}Package.json scripts
{ "scripts": { "lint": "eslint src --ext .ts,.tsx", "typecheck": "tsc --noEmit", "precommit": "npm run lint && npm run typecheck" }}Summary
In this post, I showed why CLAUDE.md rules alone are not enough for code quality. The key insight is that rules written in natural language are suggestions, not requirements.
The solution is a three-layer approach:
- Focused CLAUDE.md with specific, actionable rules
- Automated linting tools that enforce those rules consistently
- A separate reviewer agent that catches issues the coding agent misses
Start small. Identify your top 3 code quality pain points. Add targeted rules to CLAUDE.md. Configure ESLint to enforce them. Use a dedicated reviewer agent for final verification.
CLAUDE.md is one layer of defense, not the whole castle.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments