Writing CLAUDE.md

Project context files that make AI coding agents follow your rules.

Last updated

Workflow

1. The Instruction Budget

Frontier LLMs follow roughly 150–200 instructions total before compliance degrades. Claude Code's own system prompt consumes around 50 of those, leaving 100–150 for your CLAUDE.md. Every line you add competes for finite attention.

A production CLAUDE.md should land between 60 and 200 lines. Shorter files get higher per-instruction compliance; longer files dilute each rule's weight. ETH Zurich research found that auto-generated context files reduce task success by 0.5–2% while increasing token costs 20–23%. The lesson: each rule must earn its place through observed failure, not speculative coverage.

Rule of thumb: If you haven't seen the agent make the mistake at least once, the rule probably doesn't belong in CLAUDE.md.

2. What to Include vs Exclude

Include

  • Bash commands Claude can't guess (make dev, pnpm test:e2e)
  • Style rules that differ from defaults (tab width, import ordering)
  • Testing instructions and required coverage thresholds
  • Repo etiquette (branch naming, commit message format)
  • Architectural decisions the codebase doesn't make obvious
  • Dev environment quirks (required env vars, Docker prerequisites)
  • Common gotchas specific to your project

Exclude

  • Anything Claude can figure out from reading the code
  • Standard language conventions (PEP 8, Go formatting)
  • Detailed API documentation — link to it instead
  • Frequently changing information (version numbers, endpoint URLs)

Architectural overviews "did not reduce navigation time" in testing — agents explore codebases on their own. Focus on what they cannot infer from the code itself.

Never use LLMs as linters. Code style enforcement in CLAUDE.md wastes instruction budget. Use Biome, Prettier, or Ruff enforced via pre-commit hooks. The tool runs deterministically every time; the LLM does not.

3. Progressive Disclosure

Keep your root CLAUDE.md lean. Place task-specific documentation in agent_docs/ or .claude/skills/ and reference it with @path/to/import syntax. Claude loads only what's relevant to the current task.

Five loading levels

LevelPathWhen loaded
Global~/.claude/CLAUDE.mdEvery session
Project root./CLAUDE.mdEvery session in this repo
Parent dirs../CLAUDE.mdMonorepo packages inherit parent
Child dirs./src/CLAUDE.mdOn-demand when working in that dir
Rules.claude/rules/Auto-loaded, glob-matched by path

Skills (.claude/skills/) load only when the agent determines they're relevant. This keeps the active instruction count low while making deep knowledge available on demand.

Prefer pointers to copies. Reference files by path (@docs/api-conventions.md) rather than embedding snippets that go stale when the source changes.

4. AGENTS.md Cross-Tool Standard

AGENTS.md is the cross-tool equivalent of CLAUDE.md, adopted by the Linux Foundation (AAIF) and present in over 60,000 GitHub repos. It works across Codex, Cursor, Copilot, Aider, Gemini CLI, Windsurf, Zed, and 55+ other tools.

The symlink pattern

If your repo serves multiple AI agents, make CLAUDE.md a symlink to AGENTS.md so both tools read the same file:

ln -s AGENTS.md CLAUDE.md

Three-tier boundary system

TierBehaviorExample
AlwaysDo this every time, no exceptionsLint before commit
Ask FirstPause and confirm with the userDB schema changes
NeverHard block, refuse to proceedCommit secrets, force push
Codex truncation: Codex silently truncates project docs at 32 KiB. Test your file with project_doc_max_bytes and keep critical instructions in the first 32 KiB if you target Codex.

5. Hooks vs Instructions

CLAUDE.md is advisory — agents can ignore it under pressure, context limits, or ambiguity. Hooks are deterministic: they execute as code every time the specified event fires, with guaranteed behavior.

Rules that "must happen every time with zero exceptions" belong in hooks, not CLAUDE.md. The common pattern: use CLAUDE.md for preferences and guidance, hooks for enforcement and gates.

Example: block force-push via hook

// .claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hook": ".claude/hooks/block-force-push.sh"
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/block-force-push.sh
# Reads tool input from stdin, rejects force-push commands

INPUT=$(cat)
if echo "$INPUT" | grep -qE '(--force|push\s+-f)'; then
  echo "DENY: force-push is not allowed" >&2
  exit 2
fi
Exit code 2 signals a hard block — the tool call is rejected and the agent sees the denial message. Exit code 0 allows the call to proceed.
CLAUDE.md (advisory) → .claude/rules/ (auto-loaded) → Skills (on-demand) → Hooks (enforced) ~100 instructions Hierarchical configs Domain knowledge Deterministic