How CLAUDE.md Files Work — Discovery, Scoping, and Monorepo Behavior Context Explaining how the instruction file discovery algorithm works based on the actual Rust implementation at rust/crates/runtime/src/prompt.rs:192-213, with diagrams showing behavior in monorepos, sibling projects, and nested workspaces.
The Algorithm (from source code) // prompt.rs:192-213 fn discover_instruction_files(cwd: &Path) -> Vec { // 1. Collect every directory from cwd up to filesystem root // 2. Reverse so root comes first (ancestry order) // 3. For each directory, check 4 candidate filenames // 4. Deduplicate by content hash }
Step by step:
Start at current working directory (cwd) Walk upward through every parent directory to / Reverse the list (so / is first, cwd is last) For each directory, try to read these 4 files in order: {dir}/CLAUDE.md {dir}/CLAUDE.local.md {dir}/.claude/CLAUDE.md {dir}/.claude/instructions.md Skip files that don't exist or are empty Deduplicate: if two files have identical content (after whitespace normalization), keep only the first Apply budget: 4K chars max per file, 12K chars max total Diagram 1: Simple Project cwd = /home/user/myproject/src
Discovery walks UP then reverses:
/ ← checked first (4 candidates)
/home/ ← checked second
/home/user/ ← checked third
/home/user/myproject/ ← checked fourth
/home/user/myproject/src/ ← checked last (cwd)
Files found & injected in this order: /home/user/CLAUDE.md ← global user preferences /home/user/myproject/CLAUDE.md ← project instructions /home/user/myproject/src/CLAUDE.md ← subdir-specific rules
Key insight: Instructions are layered root-to-leaf. Global rules come first, specific overrides come last.
Diagram 2: Monorepo with Multiple Packages /company/monorepo/ ├── CLAUDE.md ← "Use conventional commits. Run CI before push." ├── CLAUDE.local.md ← "My local API keys are in .env.local" ├── .claude/ │ └── instructions.md ← "Shared team standards" ├── packages/ │ ├── frontend/ │ │ ├── CLAUDE.md ← "Use React 19. Prefer server components." │ │ └── src/ │ │ └── components/ │ └── backend/ │ │ ├── CLAUDE.md ← "Use Rust. Run cargo clippy before commit." │ │ └── src/ │ │ └── handlers/ │ └── shared/ │ ├── CLAUDE.md ← "This is a shared lib. Don't break API." │ └── src/ └── infra/ ├── CLAUDE.md ← "Use Terraform. Always plan before apply." └── modules/
Scenario A: Working in frontend cwd = /company/monorepo/packages/frontend/src/components
Discovered files (in injection order):
- /company/monorepo/CLAUDE.md ← repo-wide rules
- /company/monorepo/CLAUDE.local.md ← local overrides
- /company/monorepo/.claude/instructions.md ← team standards
- /company/monorepo/packages/frontend/CLAUDE.md ← frontend rules
NOT loaded: ✗ backend/CLAUDE.md ← not an ancestor of cwd ✗ shared/CLAUDE.md ← not an ancestor of cwd ✗ infra/CLAUDE.md ← not an ancestor of cwd
Scenario B: Working in backend cwd = /company/monorepo/packages/backend/src/handlers
Discovered files (in injection order):
- /company/monorepo/CLAUDE.md ← repo-wide rules
- /company/monorepo/CLAUDE.local.md ← local overrides
- /company/monorepo/.claude/instructions.md ← team standards
- /company/monorepo/packages/backend/CLAUDE.md ← backend rules
NOT loaded: ✗ frontend/CLAUDE.md ← sibling, not ancestor ✗ shared/CLAUDE.md ← sibling, not ancestor ✗ infra/CLAUDE.md ← cousin, not ancestor
Scenario C: Working at monorepo root cwd = /company/monorepo
Discovered files:
- /company/monorepo/CLAUDE.md
- /company/monorepo/CLAUDE.local.md
- /company/monorepo/.claude/instructions.md
NOT loaded: ✗ Any package-level CLAUDE.md ← they are children, not ancestors
Key monorepo insight: Sibling packages NEVER see each other's CLAUDE.md. Only the shared ancestor's files are visible to all. This is by design — it prevents cross-contamination between packages.
Diagram 3: Sibling / Cousin Projects (Separate Repos) /home/user/ ├── CLAUDE.md ← user-level defaults (applies to ALL projects) ├── project-a/ │ ├── CLAUDE.md ← project A rules │ └── src/ ├── project-b/ │ ├── CLAUDE.md ← project B rules │ └── src/ └── work/ └── client-x/ ├── CLAUDE.md ← client X rules └── services/ ├── api/ │ ├── CLAUDE.md ← API service rules │ └── src/ └── web/ ├── CLAUDE.md ← Web service rules └── src/
Working in project-a cwd = /home/user/project-a/src
Discovered:
- /home/user/CLAUDE.md ← user defaults
- /home/user/project-a/CLAUDE.md ← project A
Invisible: ✗ project-b/CLAUDE.md ← sibling project ✗ work/client-x/CLAUDE.md ← cousin project
Working in client-x API service cwd = /home/user/work/client-x/services/api/src
Discovered:
- /home/user/CLAUDE.md ← user defaults
- /home/user/work/client-x/CLAUDE.md ← client X rules
- /home/user/work/client-x/services/api/CLAUDE.md ← API rules
Invisible: ✗ services/web/CLAUDE.md ← sibling service ✗ project-a/CLAUDE.md ← unrelated project ✗ project-b/CLAUDE.md ← unrelated project
Key insight for siblings: Put shared rules in the nearest common ancestor directory. For all projects: ~/CLAUDE.md. For a client's services: client-x/CLAUDE.md.
Diagram 4: The 4-Candidate Priority per Directory For EACH directory in the ancestry chain, these 4 files are checked in order:
{dir}/
├── CLAUDE.md ← 1st: primary, checked into git
├── CLAUDE.local.md ← 2nd: local overrides, gitignored
└── .claude/
├── CLAUDE.md ← 3rd: hidden variant
└── instructions.md ← 4th: alt naming
All 4 can coexist in the same directory — they ALL get loaded (not one-wins). Deduplication only removes files with identical content.
Example: All 4 exist at project root cwd = /myproject/src
Discovered (all 4 from /myproject/ + none from /myproject/src/):
- /myproject/CLAUDE.md ← team rules (in git)
- /myproject/CLAUDE.local.md ← my local overrides (gitignored)
- /myproject/.claude/CLAUDE.md ← additional instructions
- /myproject/.claude/instructions.md ← more instructions
Budget impact: 4 files × up to 4K each = up to 12K total (exactly the budget) If a 5th file exists deeper, it gets "omitted after reaching prompt budget"
Diagram 5: Budget and Truncation MAX_INSTRUCTION_FILE_CHARS = 4,000 chars per file MAX_TOTAL_INSTRUCTION_CHARS = 12,000 chars total
Processing order (root → leaf):
File 1: /CLAUDE.md (2,000 chars) → injected fully remaining: 10,000 File 2: /proj/CLAUDE.md (5,000 chars) → truncated to 4,000 remaining: 6,000 File 3: /proj/CLAUDE.local.md (3,000) → injected fully remaining: 3,000 File 4: /proj/src/CLAUDE.md (8,000) → truncated to 3,000 remaining: 0 File 5: /proj/src/lib/CLAUDE.md → "Additional instruction content omitted"
Key insight: Root-level files get priority because they're processed first. Deep leaf files may get truncated or omitted entirely. Put your most important rules at the repo root.
Diagram 6: Deduplication Scenario: Same CLAUDE.md symlinked or copied to multiple locations
/myproject/CLAUDE.md content: "Use Rust. Run tests." /myproject/src/CLAUDE.md content: "Use Rust. Run tests." ← IDENTICAL
After deduplication (content hash comparison):
- /myproject/CLAUDE.md ← kept (seen first)
- /myproject/src/CLAUDE.md ← SKIPPED (same hash)
Whitespace normalization happens before hashing:
- Blank lines collapsed
- Leading/trailing whitespace trimmed So "Use Rust.\n\n\nRun tests.\n" == "Use Rust.\nRun tests."
Diagram 7: How Subagents See Instructions (Deep Dive)
The Spawning Chain (exact code path)
User invokes Agent tool
│
▼
execute_agent_with_spawn() tools/src/lib.rs:1347
│
├─► build_agent_system_prompt() tools/src/lib.rs:1480-1493
│ │
│ ├─► std::env::current_dir() ← gets the PROCESS cwd (same as parent)
│ │
│ ├─► load_system_prompt(cwd, ...) prompt.rs:404-418
│ │ │
│ │ ├─► ProjectContext::discover_with_git(cwd) prompt.rs:73-81
│ │ │ │
│ │ │ └─► discover_instruction_files(cwd) prompt.rs:192-213
│ │ │ │
│ │ │ └─► Walk UP from cwd to /, check 4 candidates per dir
│ │ │ Deduplicate by content hash
│ │ │
│ │ └─► SystemPromptBuilder::build() prompt.rs:134
│ │ Renders instruction files with budget (4K/file, 12K total)
│ │
│ └─► Appends: "You are a background sub-agent of type {type}..."
│
├─► AgentJob { system_prompt, prompt, allowed_tools, ... }
│
└─► spawn_agent_job(job) tools/src/lib.rs:1424-1449
│
└─► std::thread::spawn() ← NEW OS THREAD (not new process)
│
└─► build_agent_runtime(job) tools/src/lib.rs:1460-1478
│
└─► ConversationRuntime::new(
Session::new(), ← FRESH empty session
api_client, ← new API client
SubagentToolExecutor, ← restricted tool set
agent_permission_policy(),
job.system_prompt, ← ALREADY BUILT (includes CLAUDE.md)
)
Critical Detail: cwd is Inherited, Not Configurable // tools/src/lib.rs:1480-1481 fn build_agent_system_prompt(subagent_type: &str) -> Result<Vec, String> { let cwd = std::env::current_dir()...; // ← PROCESS-LEVEL cwd
The subagent uses std::env::current_dir() — the OS process's working directory. Since subagents run as threads (NOT separate processes), they share the parent's cwd. There is NO parameter to override the cwd for instruction discovery.
This means: all subagents in the same process always discover the same CLAUDE.md files.
What Each Part of the Subagent's System Prompt Contains
Subagent system prompt = [
Section 1: Base system prompt (identity, capabilities, tool descriptions)
Section 2: Project context
├── Today's date (hardcoded: DEFAULT_AGENT_SYSTEM_DATE)
├── Working directory (process cwd)
├── Instruction file count
├── Git status snapshot
└── Git diff snapshot
Section 3: Claude instructions
├── ## CLAUDE.md (scope: /monorepo) ← from root
├── ## CLAUDE.local.md (scope: /monorepo) ← local overrides
└── ## CLAUDE.md (scope: /monorepo/pkg/fe) ← from package
Section 4: Runtime config (loaded settings.json entries)
Section 5: "You are a background sub-agent of type Explore.
Work only on the delegated task, use only the tools
available to you, do not ask the user questions,
and finish with a concise result."
]
Diagram: Parent vs Subagent Instruction Visibility Process cwd = /monorepo/packages/frontend/src
┌─────────────────────────────────────────────────────────┐ │ PARENT AGENT │ │ │ │ System prompt built at startup via load_system_prompt() │ │ cwd: /monorepo/packages/frontend/src │ │ │ │ Instruction files: │ │ 1. /monorepo/CLAUDE.md ✓ │ │ 2. /monorepo/CLAUDE.local.md ✓ │ │ 3. /monorepo/packages/frontend/CLAUDE.md ✓ │ │ │ │ Session: contains full conversation history │ │ Tools: ALL tools available │ │ Permissions: user-configured mode │ └─────────────────┬───────────────────────────────────────┘ │ spawns via Agent tool ▼ ┌─────────────────────────────────────────────────────────┐ │ SUBAGENT (Thread) │ │ │ │ System prompt built via build_agent_system_prompt() │ │ cwd: /monorepo/packages/frontend/src ← SAME (shared) │ │ │ │ Instruction files: │ │ 1. /monorepo/CLAUDE.md ✓ SAME │ │ 2. /monorepo/CLAUDE.local.md ✓ SAME │ │ 3. /monorepo/packages/frontend/CLAUDE.md ✓ SAME │ │ │ │ Session: FRESH (empty, no parent history) │ │ Tools: RESTRICTED by subagent type │ │ Permissions: agent_permission_policy() (auto-allow) │ └─────────────────────────────────────────────────────────┘
What's Shared vs What's Isolated ┌──────────────────────┬────────────────────┬──────────────────────┐ │ Component │ Parent Agent │ Subagent │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ CLAUDE.md files │ discovered at │ RE-DISCOVERED at │ │ │ startup │ spawn time (same │ │ │ │ result, same cwd) │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Session / history │ full conversation │ EMPTY — fresh start │ │ │ with user │ only sees its prompt │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Available tools │ ALL tools │ SUBSET per type: │ │ │ │ Explore: read-only │ │ │ │ Verify: +bash │ │ │ │ Review: +bash │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Permission mode │ user-configured │ auto-allow (no │ │ │ (may prompt user) │ user prompting) │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Hooks (pre/post) │ configured hooks │ SAME hooks config │ │ │ run on every tool │ (shared process env) │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Git status/diff │ snapshot at start │ RE-CAPTURED at spawn │ │ │ │ (may differ if files │ │ │ │ changed since start) │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ API client/model │ user's model │ DEFAULT_AGENT_MODEL │ │ │ │ (or overridden) │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Max iterations │ unlimited │ DEFAULT_AGENT_MAX_ │ │ │ │ ITERATIONS │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Auto-compaction │ at 200K tokens │ same threshold │ │ │ │ (but unlikely to hit │ │ │ │ on single-turn task) │ ├──────────────────────┼────────────────────┼──────────────────────┤ │ Output persistence │ session files │ {agent_id}.json + │ │ │ │ {agent_id}.md in │ │ │ │ $CLAWD_AGENT_STORE │ └──────────────────────┴────────────────────┴──────────────────────┘
Monorepo Scenario: Multiple Subagents, Same Process Process cwd = /monorepo/packages/frontend/src
Parent spawns 3 subagents concurrently (all threads in same process):
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ Explore Agent │ │ Verify Agent │ │ Review Agent │ │ │ │ │ │ │ │ cwd: frontend/src │ │ cwd: frontend/src │ │ cwd: frontend/src │ │ (inherited) │ │ (inherited) │ │ (inherited) │ │ │ │ │ │ │ │ CLAUDE.md files: │ │ CLAUDE.md files: │ │ CLAUDE.md files: │ │ ✓ /monorepo/ │ │ ✓ /monorepo/ │ │ ✓ /monorepo/ │ │ ✓ /frontend/ │ │ ✓ /frontend/ │ │ ✓ /frontend/ │ │ (IDENTICAL) │ │ (IDENTICAL) │ │ (IDENTICAL) │ │ │ │ │ │ │ │ Tools: read-only │ │ Tools: read + bash │ │ Tools: read + bash │ │ Session: empty │ │ Session: empty │ │ Session: empty │ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘
ALL THREE see the exact same instruction files. They CANNOT see backend/CLAUDE.md or shared/CLAUDE.md. They have NO access to the parent's conversation history. They CAN read the same files on disk (if tools allow it).
Edge Case: What if cwd Changes Mid-Session? If something calls std::env::set_current_dir() during the session:
Parent starts: cwd = /monorepo/packages/frontend/src → discovers frontend CLAUDE.md files
Something changes cwd to: /monorepo/packages/backend/src
Subagent spawned AFTER the change: → build_agent_system_prompt() calls current_dir() → gets /monorepo/packages/backend/src → discovers BACKEND CLAUDE.md files instead!
The parent's system prompt is NOT affected (already built). But newly spawned subagents would see different instructions.
This is an edge case — cwd rarely changes mid-session, but it's possible via bash tool: cd /other/path
What Subagents CANNOT Do With Instructions Subagents CANNOT: ✗ Override which CLAUDE.md files they see (no cwd parameter) ✗ Access parent's conversation context / history ✗ See sibling subagent outputs (unless reading from disk) ✗ Modify CLAUDE.md files (Explore type has read-only tools) ✗ Skip CLAUDE.md loading (always runs full discovery) ✗ Get more than 12K chars of instructions (same budget)
Subagents CAN: ✓ Read the same CLAUDE.md files as the parent ✓ Read any file on disk (within tool permissions) ✓ Execute the prompt they were given ✓ Write output to the agent store ({agent_id}.md) ✓ Run hooks (pre/post tool use) — same config as parent
Summary: Rules of Thumb Want this? Put CLAUDE.md here Rules for ALL your projects ~/CLAUDE.md Rules for one repo {repo-root}/CLAUDE.md Rules for one package in a monorepo {repo-root}/packages/{pkg}/CLAUDE.md Local-only overrides (gitignored) {any-dir}/CLAUDE.local.md Rules for a deep subdirectory {subdir}/CLAUDE.md Shared rules for sibling packages Their common parent directory Rules siblings should NOT share Each sibling's own directory The algorithm is purely ancestor-based. It never searches sideways or downward. It only walks up from cwd to /, then injects files root-first with a 12K total budget.
Hard Limits & Constants (from source code) All values from rust/crates/runtime/src/prompt.rs and conversation.rs:
┌─────────────────────────────────────────────────────────────────────┐ │ INSTRUCTION FILE LIMITS │ ├─────────────────────────────────────┬───────────────────────────────┤ │ MAX_INSTRUCTION_FILE_CHARS │ 4,000 chars per file │ │ MAX_TOTAL_INSTRUCTION_CHARS │ 12,000 chars total │ │ Files checked per directory │ 4 (CLAUDE.md, CLAUDE.local.md,│ │ │ .claude/CLAUDE.md, │ │ │ .claude/instructions.md) │ │ Max files that fit full budget │ 3 files at 4K each = 12K │ │ Deduplication │ By content hash (after │ │ │ whitespace normalization) │ │ Truncation marker │ "\n\n[truncated]" │ │ Budget exhaustion marker │ "Additional instruction │ │ │ content omitted after │ │ │ reaching the prompt budget."│ ├─────────────────────────────────────┴───────────────────────────────┤ │ SESSION / CONTEXT LIMITS │ ├─────────────────────────────────────┬───────────────────────────────┤ │ Auto-compaction threshold │ 200,000 input tokens │ │ Compaction: preserve recent msgs │ 4 messages kept verbatim │ │ Compaction: max estimated tokens │ 10,000 tokens for summary │ │ Agent max iterations │ DEFAULT_AGENT_MAX_ITERATIONS │ ├─────────────────────────────────────┴───────────────────────────────┤ │ RENDERING INTO SYSTEM PROMPT │ ├─────────────────────────────────────┬───────────────────────────────┤ │ Section heading │ "# Claude instructions" │ │ Per-file heading format │ "## {filename} (scope: {dir})"│ │ Processing order │ Root → leaf (ancestor first) │ │ Content normalization │ Blank lines collapsed, │ │ │ leading/trailing trim │ └─────────────────────────────────────┴───────────────────────────────┘
Writing Effective CLAUDE.md Files (Data-Driven Guidelines) Based on the code's behavior, here's how to maximize the impact of your instruction files:
- Stay Under 4,000 Characters Per File prompt.rs:39 → MAX_INSTRUCTION_FILE_CHARS = 4_000 prompt.rs:366 → truncate_instruction_content() hard-caps at this limit
If your file is 4,001 chars: → First 4,000 chars kept → "\n\n[truncated]" appended → Everything after char 4,000 is LOST
Character count, NOT token count. Roughly: 4,000 chars ≈ 600-800 words ≈ ~1,000 tokens
Practical tip: Keep each CLAUDE.md under 3,500 chars to leave room for the heading and scope annotation that get added during rendering.
- Budget Your Total Across All Files: 12,000 Characters prompt.rs:40 → MAX_TOTAL_INSTRUCTION_CHARS = 12_000 prompt.rs:303 → render_instruction_files() tracks remaining_chars
Budget consumption order (root files eat budget first):
File at / → consumes from 12,000 budget File at /home/ → consumes from remaining budget File at /home/proj/ → consumes from remaining budget File at /home/proj/src/ → may get truncated or omitted entirely!
Maximum effective layout: 3 files × 4,000 chars = 12,000 (full budget used) 4th file onwards → "Additional instruction content omitted"
Practical tip: In a monorepo, you effectively get:
~4K for repo root CLAUDE.md (shared rules) ~4K for CLAUDE.local.md or .claude/instructions.md (team/local rules) ~4K for the specific package/directory CLAUDE.md Anything beyond this gets dropped 3. Put Critical Rules at the Top of Each File prompt.rs:366-376 → truncation takes FIRST N chars, drops the rest
If your CLAUDE.md is: Line 1-50: Important rules ← KEPT Line 51-100: Nice-to-have rules ← KEPT Line 101+: Extra context ← MIGHT BE TRUNCATED
The system does NOT do smart summarization — it's a hard character cut.
Practical tip: Structure your CLAUDE.md with the most important rules first. Treat it like an inverted pyramid — essential info at the top, supplementary details below.
- Put the Most Important File at the Root Processing is root-to-leaf. The root file ALWAYS gets full budget priority.
Good: /monorepo/CLAUDE.md (3,000 chars) → fully injected, 9K remaining /monorepo/pkg/CLAUDE.md (3,000 chars) → fully injected, 6K remaining
Bad: ~/CLAUDE.md (4,000 chars) → fully injected, 8K remaining /monorepo/CLAUDE.md (4,000 chars) → fully injected, 4K remaining /monorepo/CLAUDE.local.md (4,000 chars) → fully injected, 0 remaining /monorepo/pkg/CLAUDE.md (2,000 chars) → OMITTED! Budget exhausted!
Practical tip: If you use ~/CLAUDE.md for global preferences, keep it short (under 1K) so it doesn't eat into project-specific budgets.
- Use CLAUDE.local.md for Personal/Secret Overrides Each directory checks 4 files. All 4 are loaded independently:
CLAUDE.md → shared, committed to git CLAUDE.local.md → personal, add to .gitignore .claude/CLAUDE.md → hidden variant .claude/instructions.md → alt naming
CLAUDE.local.md is ideal for:
- Local paths, API endpoints, environment names
- Personal preferences that differ from team
- Machine-specific instructions
Practical tip: Add CLAUDE.local.md to your .gitignore and use it for overrides that shouldn't be shared.
- Avoid Duplicate Content Across Files prompt.rs:326-341 → dedupe_instruction_files()
Content is normalized before hashing:
- Blank lines collapsed to single blank line
- Leading/trailing whitespace trimmed
- Then hashed with DefaultHasher
If root/CLAUDE.md and root/src/CLAUDE.md have identical content: → Only root/CLAUDE.md is kept (it's seen first) → src/CLAUDE.md is silently dropped → Budget is NOT consumed by the duplicate
Practical tip: Don't copy the same CLAUDE.md into subdirectories "for safety." It wastes nothing (deduplication catches it), but it also adds nothing. Put shared rules in the ancestor and specific rules in the descendant.
- How Your File Appears in the System Prompt The rendered output looks like:
[Your content here, up to 4K chars]
[Your content here, up to 4K chars]
The filename is extracted via display_context_path() → just the filename, not the full path. The scope shows the parent directory path.
Practical tip: Since Claude sees the scope annotation, you can reference it in your instructions: "When working in this scope, prefer..." — Claude will see which directory the instruction came from.
-
What the System Prompt Already Contains (don't repeat this) The system prompt is built in this order (prompt.rs:134-156):
-
Intro section → "You are an interactive agent..."
-
Output style → (if configured)
-
System section → tool permissions, compression notice, hooks
-
Doing tasks section → "Read code before changing it", "no speculative abstractions", "no unnecessary files", "diagnose failures", "no security vulns", "report faithfully"
-
Actions section → "consider reversibility and blast radius"
-
── DYNAMIC BOUNDARY ──
-
Environment section → OS, cwd, date
-
Project context → git status, git diff, instruction file count
-
Claude instructions → YOUR CLAUDE.md FILES GO HERE
-
Runtime config → loaded settings.json entries
-
Appended sections → (additional context)
Practical tip: Don't repeat in CLAUDE.md what the system prompt already says. You don't need to say "read code before changing it" or "be careful with security" — that's already injected. Use your 4K budget for project-specific knowledge the system doesn't have.
- Ideal CLAUDE.md Structure (Template)
- Language: Rust
- Framework: Axum
- Database: PostgreSQL via sqlx
- Build:
cargo build --workspace - Test:
cargo test --workspace - Lint:
cargo clippy -- -D warnings - Format:
cargo fmt --check
- API handlers: src/api/
- Domain logic: src/domain/
- Database: src/db/ (all queries use sqlx macros)
- All new endpoints need integration tests
- Use thiserror for error types, not anyhow
- Database migrations go in migrations/
- Error handling: return Result<T, AppError>
- Auth: extract user from JWT middleware, never query directly
- Pagination: use Cursor from src/pagination.rs
Total: ~2,050 chars — well within the 4K limit, leaves ~10K budget for deeper CLAUDE.md files in subdirectories.
- Subagent-Aware Writing Subagents see your CLAUDE.md files but have:
- NO conversation history (fresh session)
- RESTRICTED tools (e.g., Explore agents can only read)
- NO ability to ask the user questions
This means your CLAUDE.md should be SELF-CONTAINED: ✓ Include build/test commands (agent can't ask "how do I run tests?") ✓ Include key file paths (agent can't infer from conversation context) ✓ Include architectural rules (agent has no prior context about decisions)
✗ Don't assume prior conversation ("as we discussed..." means nothing) ✗ Don't reference dynamic state ("the current branch..." may differ)