Context Engineering for Claude: How to Train It on Your Codebase
Context engineering is the practice of deliberately structuring what Claude sees in its context window — not just what you ask, but what background information Claude has when it answers. For Claude Code, this means curating CLAUDE.md, using @file references strategically, and loading the right architectural context before asking for implementation. Done well, it eliminates the "Claude doesn't understand my codebase" problem entirely.
Why Context Engineering Matters More Than Prompt Engineering
Most developers focus on how they phrase requests. Context engineering focuses on what Claude already knows before you ask.
Without context engineering:
You: "Add pagination to the user list endpoint"
Claude: "I'll use an offset-based approach. Here's the implementation..."
[Uses wrong DB library, ignores your existing pagination pattern, generates type errors]
With context engineering:
CLAUDE.md includes:
- "Use cursor-based pagination (not offset) — see lib/db/pagination.ts for the pattern"
- "All list endpoints follow the pattern in app/api/items/route.ts"
You: "Add pagination to the user list endpoint"
Claude: "I'll use cursor-based pagination consistent with your existing pattern..."
[Generates code that matches your conventions perfectly]
The difference isn't the request — it's what Claude already knows.
Layer 1: CLAUDE.md — The Persistent Context Layer
CLAUDE.md is loaded automatically every time Claude Code starts in your project. It's your primary context engineering tool.
What belongs in CLAUDE.md
High-value content:
- Architecture decisions — not just what was chosen but why
- Conventions and anti-patterns — the exact things Claude gets wrong without guidance
- Key file map — where to find important patterns
- Domain vocabulary — project-specific terms and their meanings
Low-value content (skip these):
- Technology descriptions Claude already knows (e.g., "we use TypeScript")
- General best practices (e.g., "write clean code")
- Things that are obvious from the code itself
High-signal CLAUDE.md example
# Project Context
## Architecture
This is a multi-tenant SaaS. Every DB query MUST filter by `organizationId`.
Forgetting this causes cross-tenant data leaks. Before writing any DB query,
check: does it include `.where(eq(schema.table.organizationId, ctx.organizationId))`?
## Key Patterns (read these before implementing anything similar)
- Auth: middleware.ts — how session validation works
- DB queries: lib/db/queries/users.ts — the pattern all queries follow
- Server actions: app/actions/projects.ts — the validation + mutation pattern
- Error handling: lib/errors.ts — our custom error types and how to throw them
## Domain Vocabulary
- "workspace" = an organization account (not a personal workspace)
- "member" = a user within a workspace (User is the auth identity)
- "resource" = any workspace-owned entity (projects, templates, etc.)
## Anti-patterns (Never Do This)
- Never use `userId` directly in DB queries — always go through `organizationId` first
- Never return raw DB errors to the client — use our `AppError` class
- Never add `"use client"` to a component that doesn't need browser APIs
- Never skip the `bun run typecheck` step before saying a task is complete
Layer 2: @file References — Targeted Context Loading
During a session, use @filename to load specific files into context before complex tasks.
Pattern: Reference-before-implement
@lib/db/queries/users.ts @lib/db/schema.ts
Now implement a query function that returns all active projects for an organization,
sorted by lastModifiedAt descending, with pagination using the same cursor pattern.
This ensures Claude sees the exact patterns you want replicated before it writes any code.
Pattern: Error-plus-context
This error is happening:
[paste error]
@app/api/webhooks/route.ts @lib/stripe/webhook-handlers.ts
What's causing this and how do I fix it?
Loading the relevant files alongside the error gives Claude the context to diagnose precisely rather than guess.
Which files to reference
| Situation | Files to reference |
|---|---|
| Adding a new API endpoint | Existing endpoint of same type |
| Adding a new DB query | schema.ts + existing query of same type |
| Debugging an error | File with error + direct dependencies |
| Adding a new component | Similar existing component |
| Writing tests | Test file for similar functionality |
Layer 3: Project Summary Files
For large codebases, maintaining ARCHITECTURE.md and DECISIONS.md as separate files that you reference on complex tasks.
ARCHITECTURE.md structure
# Architecture Overview
## System Diagram
[ASCII or text description of how services connect]
## Request Flow
1. Client → Vercel Edge → Next.js middleware (auth check)
2. Middleware → App Router page/route
3. Route → Server Action → Drizzle query → Neon DB
4. Response → Server Component render → Client
## Key Invariants
- All mutations go through Server Actions (no direct DB calls from components)
- Session validation happens in middleware — routes assume valid session
- Billing gates are checked in Server Actions, not middleware
## Bounded Contexts
- Auth: handled entirely by Clerk (we don't touch auth logic)
- Billing: Stripe webhooks → our DB (never call Stripe from frontend)
- Email: Resend SDK called from server actions only
DECISIONS.md — the "why" record
# Architecture Decisions
## 2026-02-01: Cursor-based pagination instead of offset
**Why**: Offset pagination is O(n) — scanning all rows before the offset.
At 100k+ records, page 500 becomes unacceptably slow.
Cursor pagination is O(1) regardless of dataset size.
**Trade-off**: You can't jump to an arbitrary page number.
**Implementation**: lib/db/pagination.ts
## 2026-03-15: Drizzle over Prisma
**Why**: Drizzle SQL stays closer to actual queries — easier to optimize.
Prisma's abstraction hides performance issues until production.
**Trade-off**: More verbose queries, no automatic cascade handling.
Reference these in complex tasks:
@ARCHITECTURE.md @DECISIONS.md
I need to add real-time presence indicators (show who's online in a workspace).
How should I implement this given our architecture? What are the trade-offs?
Layer 4: Dynamic Context Injection
For automated or programmatic Claude usage, inject context programmatically.
Python: Injecting relevant file contents
import anthropic
from pathlib import Path
client = anthropic.Anthropic()
def load_context_files(*paths: str) -> str:
"""Load multiple files into a formatted context string."""
context_parts = []
for path in paths:
content = Path(path).read_text()
context_parts.append(f"=== {path} ===\n{content}")
return "\n\n".join(context_parts)
def ask_with_context(task: str, context_files: list[str]) -> str:
"""Ask Claude with specific files pre-loaded."""
context = load_context_files(*context_files)
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
system=f"""You are working on this codebase.
Here are the relevant files for context:
{context}
Follow the patterns established in these files exactly.""",
messages=[{"role": "user", "content": task}]
)
return response.content[0].text
# Usage
result = ask_with_context(
task="Add a getProjectsByWorkspace query following the same pattern as getUsersByOrg",
context_files=[
"lib/db/schema.ts",
"lib/db/queries/users.ts", # The pattern to follow
]
)
Codebase summary generation
For very large codebases, generate a summary Claude can reference:
def generate_codebase_summary(src_dir: str, max_tokens: int = 4000) -> str:
"""Generate a compact codebase summary for context injection."""
all_files = list(Path(src_dir).rglob("*.ts")) + list(Path(src_dir).rglob("*.tsx"))
# Filter to key files (skip test files, generated files)
key_files = [
f for f in all_files
if not any(skip in str(f) for skip in ["node_modules", ".next", "test", "spec"])
][:20] # Take top 20 most relevant
file_summaries = []
for file_path in key_files:
# Get just the exports/function signatures, not implementations
content = file_path.read_text()
first_lines = "\n".join(content.split("\n")[:10])
file_summaries.append(f"{file_path}: {first_lines[:200]}")
return "\n".join(file_summaries)
Context Window Budget Management
Claude's context window is 200k tokens — large, but not infinite. Use it deliberately.
What consumes tokens
| Content | Rough size |
|---|---|
| CLAUDE.md (good, lean) | 500-1,000 tokens |
| One medium file (200 lines) | 600-800 tokens |
| Full codebase reference | 20,000-100,000 tokens |
| Long conversation history | 5,000-30,000 tokens |
| Tool definitions (5 tools) | 300-600 tokens |
The budget rule
Save 40-50% of context for:
- The actual task description
- Claude's response
- Follow-up turns
This means you have ~100k tokens for reference material in a long working session.
Context pruning during long sessions
If a session runs long, prune unnecessary context:
We've been working on the auth system. Let's move to the payment module now.
Ignore everything we discussed about auth — that context is no longer relevant.
@lib/stripe/index.ts @app/api/webhooks/route.ts
New task: [payment module task]
Measuring Context Engineering Quality
A simple self-test: ask Claude to describe your project after loading context.
Based on CLAUDE.md and the files you've seen, describe:
1. What this project does (2 sentences)
2. The main architectural patterns in use
3. The most important conventions to follow
4. What you should NOT do based on the anti-patterns listed
If Claude's answer is accurate and specific, your context engineering is working. If it's vague or wrong, your CLAUDE.md needs refinement.
Frequently Asked Questions
What is context engineering for Claude? Context engineering is the practice of structuring the background information in Claude's context window — using CLAUDE.md, @file references, and project documentation to ensure Claude understands your codebase conventions before you ask it to write or modify code.
How is context engineering different from prompt engineering? Prompt engineering focuses on how you phrase a request. Context engineering focuses on what Claude already knows before you ask. Good context engineering means you need less careful prompting because Claude already understands your conventions.
How long should CLAUDE.md be? Aim for 300-800 tokens (roughly 1-2 pages). Longer files lose effectiveness because Claude weighs all parts equally — more content means each part gets less weight. Focus on anti-patterns and non-obvious conventions, not general best practices.
Can I have multiple CLAUDE.md files in subdirectories? Yes. Claude Code reads CLAUDE.md files hierarchically — the root CLAUDE.md plus any CLAUDE.md in subdirectories you're working in. Use this for monorepos: root CLAUDE.md for global conventions, package-specific CLAUDE.md for package-level patterns.
Does conversation history count toward the context window? Yes. A long multi-turn session accumulates history that consumes context tokens. For very long sessions, start a fresh session for unrelated work to avoid irrelevant history occupying context.
Related Guides
- Claude Code Complete Guide — Full Claude Code reference
- Claude Code for Teams: Best Practices — Team CLAUDE.md patterns
- Startup MVP with Claude Code in 1 Week — Practical context setup
Go Deeper
Power Prompts 300 — $29 — Includes 50 context engineering patterns: CLAUDE.md templates for different project types, @file reference patterns, and context loading sequences that get consistent, convention-following code output.
30-day money-back guarantee. Instant download.