Originally published at claudeguide.io/context-engineering-claude
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 in 2026. 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
python
import anthropic
from pathlib import Path
client = anthropic.Anthropic()
def load_context_files(*paths: str) -
[→ Get Power Prompts 300 — $29](https://shoutfirst.gumroad.com/l/agfda?utm_source=claudeguide&utm_medium=article&utm_campaign=context-engineering-claude)
*30-day money-back guarantee. Instant download.*
Top comments (0)