Best Practices for Claude Code
Getting the highest output from Claude Code comes down to three operational principles: tell Claude what it cannot discover by reading the repo, give it an executable verification check to run, and enforce non-negotiable standards with deterministic hooks.
1. Deterministic vs Non-Deterministic Instructions
Every instruction in Claude Code falls into one of two operational domains:
| Category | Non-Deterministic (Claude decides) | Deterministic (Claude Code harness enforces) |
|---|---|---|
| What to do | User prompts, CLAUDE.md guidelines, skills | Lifecycle hooks, strict skills (disable-model-invocation: true) |
| What it can touch | Auto mode classifier judgments | Deny rules, strict sandbox mode |
| When it is done | Claude outputting "Done!" | Compiler checks, unit tests, Stop hooks |
| How much it spends | Model reasoning effort level | --max-turns, --max-budget-usd flags |
Rule of Thumb: If you keep adding "IMPORTANT" or ALL CAPS to an instruction in CLAUDE.md and Claude still occasionally misses it, the instruction does not belong in a prompt. It belongs in a deterministic hook or a deny rule.2. Tell Claude What It Cannot See
Modern models are exceptionally good at discovering code conventions, folder structures, and frameworks simply by reading your files. Do not waste prompt tokens explaining what is already obvious in the repository.
Only write down what Claude cannot discover on its own:
- A deployment sequence that lives only in engineering tribal knowledge.
- A directory that appears deprecated but is actively utilized in production.
- The exact command to run a single isolated test file.
- Environment variables required for local integration tests.
3. Keep CLAUDE.md Short (<200 Lines)
CLAUDE.md is injected into every single conversational turn. If it grows to 800 lines, it dilutes the model's attention and consumes prompt tokens needlessly. An elite CLAUDE.md remains under 200 lines:
# Commands - Install: pnpm install - Run one test: pnpm vitest run path/to/file.test.ts - Typecheck: pnpm tsc --noEmit # Conventions - Use error helpers in src/lib/errors.ts; never throw raw strings - src/legacy/ is active; do not duplicate its patterns into new endpoints # Gotchas - Integration tests require DATABASE_URL set (see .env.example)4. Scope Rules with .claude/rules/
If an architectural guideline applies only to one subsystem (such as database migrations or API schemas), place it in .claude/rules/ with a paths: frontmatter matcher:
--- paths: - "src/api/**/*.ts" --- - Validate all incoming request payloads with Zod schemas in src/api/schemas/ - New endpoints must register an OpenAPI route in docs/openapi.yaml5. Enforce Verification with Stop Hooks
A Stop hook executes whenever Claude attempts to end its conversational turn. If your test or typecheck fails, the hook exits with code 2 and passes compiler stderr back to Claude, compelling the model to repair the error:
// .claude/settings.json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "pnpm tsc --noEmit 1>&2 || exit 2"
}
]
}
]
}
}6. Deny Rules for Sensitive Files
Never rely on a prompt to protect environment secrets. Define explicit deny rules in .claude/settings.json:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.local)",
"Read(./secrets/**)",
"Bash(git push *)"
]
}
}Deny rules are enforced deterministically across all permission modes, including Auto mode and bypassPermissions.
7. What You Can Stop Doing (Outdated Advice)
| Outdated Habit | Modern Claude Code Reality |
|---|---|
| Micro-managing the context window | Models feature 1M+ token windows; compaction runs automatically when needed. |
| Using ALL CAPS or "IMPORTANT" | Current models follow plain language. Non-negotiable rules belong in hooks or deny rules. |
Describing entire architecture in CLAUDE.md | Claude reads the repository itself. Only document what is not in the code. |
| Prompting "think step by step" | Use the native /effort command (medium, high, xhigh) or ultrathink. |