DEV Community

Olivia Craft
Olivia Craft

Posted on

CLAUDE.md vs System Prompt: What Actually Controls Claude Behavior

CLAUDE.md vs System Prompt: What Actually Controls Claude Behavior

You set up a system prompt. You write a CLAUDE.md file. They say different things.

Which one wins?

This is the most common confusion I see from developers using Claude Code. The system prompt and CLAUDE.md serve different purposes, operate at different levels, and have different enforcement characteristics. Understanding the difference is the key to getting consistent, predictable behavior from Claude.


What is a system prompt?

A system prompt is the instruction block passed to Claude via the API at the start of every conversation. It's ephemeral — it exists only for that API call.

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    system="You are a helpful coding assistant. Always use TypeScript.",
    messages=[{"role": "user", "content": "Write a function to parse JSON"}]
)
Enter fullscreen mode Exit fullscreen mode

The system prompt tells Claude how to behave in this conversation. It's powerful, but it has limits:

  • It resets every session
  • It's invisible to other tools in the workflow
  • It can't enforce rules across files or projects
  • It requires API-level access to modify

What is CLAUDE.md?

CLAUDE.md is a file that lives in your project directory. Claude Code reads it automatically at the start of every session. It acts as persistent, project-scoped instructions.

# CLAUDE.md

## Rules
- Use TypeScript strict mode for all files
- Never use `any` type — use `unknown` or explicit types
- All functions must have JSDoc comments
- Prefer named exports over default exports

## Architecture
- API routes go in `src/api/`
- Shared types go in `src/types/`
- Tests mirror source structure in `__tests__/`
Enter fullscreen mode Exit fullscreen mode

CLAUDE.md is different from a system prompt in three key ways:

  1. It persists — it's a file in your repo, versioned with git
  2. It's project-scoped — rules apply to everyone working on the project
  3. It's hierarchical — you can have CLAUDE.md files at different directory levels

Which one wins when they conflict?

Here's what actually happens when CLAUDE.md and a system prompt disagree.

Short answer: CLAUDE.md instructions are treated as high-priority project context. They sit alongside the system prompt in Claude's context, but because they're specific and scoped to the project, Claude tends to follow them over generic system prompt instructions.

Let's see this in practice.

Example 1: Coding style conflict

System prompt:

Always use single quotes for strings.
Enter fullscreen mode Exit fullscreen mode

CLAUDE.md:

## Style
- Use double quotes for all strings
- Enforce via ESLint: quotes: ["error", "double"]
Enter fullscreen mode Exit fullscreen mode

Result: Claude follows CLAUDE.md and uses double quotes. The project-level rule with a concrete enforcement mechanism (ESLint config reference) carries more weight than a generic system instruction.

Example 2: Framework preference conflict

System prompt:

When building React components, use class components for stateful logic.
Enter fullscreen mode Exit fullscreen mode

CLAUDE.md:

## Rules
- All React components must be functional components
- Use hooks for all state management
- Class components are banned — refactor any you find
Enter fullscreen mode Exit fullscreen mode

Result: Claude follows CLAUDE.md. The word "banned" plus the refactoring instruction makes this an unambiguous project rule, not a preference.

Example 3: Testing approach conflict

System prompt:

Write unit tests using Jest with shallow rendering.
Enter fullscreen mode Exit fullscreen mode

CLAUDE.md:

## Testing
- Use Vitest, not Jest
- Use React Testing Library with full rendering
- Test user behavior, not implementation details
- Never use shallow rendering
Enter fullscreen mode Exit fullscreen mode

Result: Claude follows CLAUDE.md. The explicit tool choice (Vitest) and the direct "never use" instruction override the system prompt's suggestion.


CLAUDE.md enforcement patterns that actually work

After months of iteration, these are the patterns that produce consistent Claude behavior.

Pattern 1: Negative rules with alternatives

Don't just say what not to do — say what to do instead.

Weak:

- Don't use `var`
Enter fullscreen mode Exit fullscreen mode

Strong:

- Never use `var` — use `const` by default, `let` only when reassignment is needed
Enter fullscreen mode Exit fullscreen mode

Claude follows the strong version consistently because it has a clear decision path.

Pattern 2: Rules with enforcement hooks

Reference your actual tooling. This tells Claude the rule is enforced, not just preferred.

Before:

- Use consistent formatting
Enter fullscreen mode Exit fullscreen mode

After:

- Format all code with Prettier (config in .prettierrc)
- Run `npm run lint` before considering any task complete
- ESLint strict mode is enabled — zero warnings allowed
Enter fullscreen mode Exit fullscreen mode

When Claude sees that tooling enforces a rule, it treats the rule as non-negotiable.

Pattern 3: Architecture rules with file paths

Abstract rules get ignored. Concrete rules with paths get followed.

Before:

- Keep the codebase organized
Enter fullscreen mode Exit fullscreen mode

After:

## File structure
- API routes: `src/routes/[resource].ts`
- Database models: `src/models/[Model].ts`
- Shared utilities: `src/utils/` (no subdirectories)
- Component tests: `src/components/__tests__/[Component].test.tsx`
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Scoped CLAUDE.md files

You can place CLAUDE.md files in subdirectories. Claude Code reads the nearest one to the file being edited.

project/
├── CLAUDE.md              # Global rules
├── src/
│   ├── CLAUDE.md          # Frontend-specific rules
│   └── components/
└── server/
    ├── CLAUDE.md          # Backend-specific rules
    └── routes/
Enter fullscreen mode Exit fullscreen mode

This lets you enforce different conventions in different parts of the codebase without conflict.

Pattern 5: Boundary definitions

Tell Claude where to stop. This prevents scope creep in generated code.

## Boundaries
- Do NOT modify files in `src/legacy/` — migration in progress
- Do NOT add new dependencies without explicit approval
- Do NOT refactor code outside the scope of the current task
- Keep changes focused — one logical change per commit
Enter fullscreen mode Exit fullscreen mode

Practical setup: combining both effectively

The best setup uses system prompts and CLAUDE.md for different things:

System prompt — session-level behavior:

You are a senior TypeScript developer. Be concise. 
Ask clarifying questions before making large changes.
Enter fullscreen mode Exit fullscreen mode

CLAUDE.md — project-level rules:

## Stack
- TypeScript 5.x, React 19, Next.js 15
- Prisma ORM with PostgreSQL
- Vitest + React Testing Library

## Rules
- Strict TypeScript — no `any`, no `as` assertions
- All API routes must validate input with Zod
- All components must have corresponding test files
- Use server components by default, client components only when needed

## Architecture
- App router: `src/app/`
- Shared components: `src/components/`
- Server actions: `src/actions/`
- Database schema: `prisma/schema.prisma`
Enter fullscreen mode Exit fullscreen mode

The system prompt shapes how Claude talks to you. CLAUDE.md shapes what Claude builds.


Quick reference

Feature System Prompt CLAUDE.md
Scope Single conversation Entire project
Persistence Ephemeral Git-versioned
Who controls it API caller Any team member
Best for Behavior/tone Code rules/architecture
Conflict resolution Lower priority Higher priority
Supports hierarchy No Yes (nested directories)

Start with tested rules

Writing effective CLAUDE.md rules from scratch takes iteration. If you want a head start, I've published a CLAUDE.md Rules Pack with 50+ production-tested rules covering TypeScript, React, Next.js, Prisma, and testing conventions — organized by the enforcement patterns described above.

If you're also using Cursor alongside Claude Code, the Cursor Rules Pack v2 follows the same structure for .cursorrules files. Both packs are designed to work together so your AI tools stay consistent across your workflow.


The takeaway

System prompts and CLAUDE.md are not competing — they're complementary. Use system prompts for conversation-level behavior. Use CLAUDE.md for project-level enforcement. When they conflict, CLAUDE.md wins because it's specific, persistent, and scoped.

The developers getting the best results from Claude aren't writing longer prompts. They're writing better CLAUDE.md files.

Top comments (0)